diff --git a/siriuspy/siriuspy/VERSION b/siriuspy/siriuspy/VERSION index 3478b1da0..ece2083a6 100644 --- a/siriuspy/siriuspy/VERSION +++ b/siriuspy/siriuspy/VERSION @@ -1 +1 @@ -2.83.1 +2.87.1 diff --git a/siriuspy/siriuspy/clientarch/client.py b/siriuspy/siriuspy/clientarch/client.py index 189ccd61f..fa6c4f0d2 100644 --- a/siriuspy/siriuspy/clientarch/client.py +++ b/siriuspy/siriuspy/clientarch/client.py @@ -1,7 +1,9 @@ -#!/usr/bin/env python-sirius """Fetcher module. -See https://slacmshankar.github.io/epicsarchiver_docs/userguide.html +See + https://slacmshankar.github.io/epicsarchiver_docs/userguide.html + http://slacmshankar.github.io/epicsarchiver_docs/details.html + http://slacmshankar.github.io/epicsarchiver_docs/api/mgmt_scriptables.html """ from threading import Thread as _Thread @@ -16,6 +18,7 @@ from .. import envars as _envars from . import exceptions as _exceptions +from .time import Time as _Time class ClientArchiver: @@ -134,6 +137,27 @@ def getPausedPVsReport(self): resp = self._make_request(url, return_json=True) return None if not resp else resp + def getRecentlyModifiedPVs(self, limit=None, epoch_time=True): + """Get list of PVs with recently modified PVTypeInfo. + + Currently version of the epics archiver appliance returns pvname + list from oldest to newest modified timestamps.""" + method = 'getRecentlyModifiedPVs' + # get data + if limit is not None: + method += f'?limit={str(limit)}' + url = self._create_url(method=method) + resp = self._make_request(url, return_json=True) + + # convert to epoch, if the case + if resp and epoch_time: + for item in resp: + modtime = item['modificationTime'][:-7] # remove ISO8601 offset + epoch_time = _Time.conv_to_epoch(modtime, '%b/%d/%Y %H:%M:%S') + item['modificationTime'] = epoch_time + + return None if not resp else resp + def pausePVs(self, pvnames): """Pause PVs.""" if not isinstance(pvnames, (list, tuple)): diff --git a/siriuspy/siriuspy/clientarch/pvarch.py b/siriuspy/siriuspy/clientarch/pvarch.py index d481d552a..ad0d81a22 100644 --- a/siriuspy/siriuspy/clientarch/pvarch.py +++ b/siriuspy/siriuspy/clientarch/pvarch.py @@ -112,9 +112,11 @@ def is_archived(self): return False return True - def update(self): + def update(self, timeout=None): """.""" self.connect() + if timeout is not None: + self.timeout = timeout data = self.connector.getPVDetails(self.pvname) if not data: return False @@ -280,9 +282,11 @@ def severity(self): """Severity data.""" return self._severity - def update(self, mean_sec=None, parallel=True): + def update(self, mean_sec=None, parallel=True, timeout=None): """Update.""" self.connect() + if timeout is not None: + self.timeout = timeout if None in (self.timestamp_start, self.timestamp_stop): print('Start and stop timestamps not defined! Aborting.') return @@ -493,9 +497,11 @@ def parallel_query_bin_interval(self, new_intvl): self._pvdata[pvname].parallel_query_bin_interval = \ self._parallel_query_bin_interval - def update(self, mean_sec=None, parallel=True): + def update(self, mean_sec=None, parallel=True, timeout=None): """Update.""" self.connect() + if timeout is not None: + self.timeout = None if None in (self.timestamp_start, self.timestamp_stop): print('Start and stop timestamps not defined! Aborting.') return diff --git a/siriuspy/siriuspy/clientarch/time.py b/siriuspy/siriuspy/clientarch/time.py index 899573812..9983cd1e5 100644 --- a/siriuspy/siriuspy/clientarch/time.py +++ b/siriuspy/siriuspy/clientarch/time.py @@ -2,6 +2,7 @@ from . import exceptions as _exceptions from datetime import datetime as _datetime, timedelta as _timedelta +from calendar import timegm as _timegm class Time(_datetime): @@ -94,6 +95,13 @@ def __sub__(self, other): sub = super().__sub__(other) return Time(timestamp=sub.timestamp()) + @staticmethod + def conv_to_epoch(time, datetime_format): + """get epoch from datetime.""" + utc_time = _datetime.strptime(time, datetime_format) + epoch_time = _timegm(utc_time.timetuple()) + return epoch_time + def get_time_intervals( time_start, time_stop, interval, return_isoformat=False): diff --git a/siriuspy/siriuspy/clientconfigdb/configdb_client.py b/siriuspy/siriuspy/clientconfigdb/configdb_client.py index 730199da0..2cb93034c 100644 --- a/siriuspy/siriuspy/clientconfigdb/configdb_client.py +++ b/siriuspy/siriuspy/clientconfigdb/configdb_client.py @@ -3,6 +3,7 @@ import json as _json import datetime as _datetime +import re as _re from urllib import parse as _parse from urllib.request import Request as _Request, urlopen as _urlopen from urllib.error import URLError as _URLError @@ -170,6 +171,44 @@ def check_valid_value(self, value, config_type=None): config_type = self._process_config_type(config_type) return _templates.check_value(config_type, value) + @staticmethod + def compare_configs(config1, config2, pos_pattern=None, neg_pattern=None): + """Compare "pvs" attribute of configs 1 and 2. + + Print the differences. + + Args: + config1 (dict): Valid configuration with "pvs" attribute. + config2 (dict): Valid configuration with "pvs" attribute. + pos_pattern (str, optional): Only PVs with this pattern will be + checked. Defaults to None. None means all PVs will be compared. + neg_pattern (_type_, optional): PVs which match this pattern will + not be checked. Defaults to None. None means no PV will be + excluded from comparison. In case of conflict with + `pos_pattern`, it will take precedence. + """ + pos_re = _re.compile('' if pos_pattern is None else pos_pattern) + # In case of None in neg_pattern, create a regexp that never matches: + # https://stackoverflow.com/questions/1723182/a-regex-that-will-never-be-matched-by-anything + neg_re = _re.compile(r'(?!x)x' if neg_pattern is None else neg_pattern) + + cf1pvs = {n: v for n, v, _ in config1['pvs']} + cf2pvs = {n: v for n, v, _ in config2['pvs']} + + lines = [] + for pv in cf1pvs.keys() | cf2pvs.keys(): + if not pos_re.match(pv) or neg_re.match(pv): + continue + val1 = str(cf1pvs.get(pv, 'Not present')) + val2 = str(cf2pvs.get(pv, 'Not present')) + if val1 != val2: + lines.append(f'{pv:50s} {val1[:30]:30s} {val2[:30]:30s}') + + # sort and print lines + lines = sorted(lines) + for line in lines: + print(line) + @classmethod def check_valid_configname(cls, name): "Check if `name` is a valid name for configurations." diff --git a/siriuspy/siriuspy/clientconfigdb/types/as_rf.py b/siriuspy/siriuspy/clientconfigdb/types/as_rf.py index 51a578752..9cba69604 100644 --- a/siriuspy/siriuspy/clientconfigdb/types/as_rf.py +++ b/siriuspy/siriuspy/clientconfigdb/types/as_rf.py @@ -1,5 +1,8 @@ """AS RF configuration.""" from copy import deepcopy as _dcopy +import numpy as _np + +az = _np.zeros(5) # NOTE: absolute imports are necessary here due to how # CONFIG_TYPES in __init__.py is built. @@ -24,7 +27,8 @@ def get_dict(): # delay [s] the client should wait before setting the next PV. _pvs_bo_llrf = [ - ['BR-RF-DLLRF-01:ILK:REVSSA1:S', 0.0, 0.0], # Interlock disable + # Interlock disable + ['BR-RF-DLLRF-01:ILK:REVSSA1:S', 0.0, 0.0], ['BR-RF-DLLRF-01:ILK:REVSSA2:S', 0, 0.0], ['BR-RF-DLLRF-01:ILK:REVSSA3:S', 0, 0.0], ['BR-RF-DLLRF-01:ILK:REVSSA4:S', 0, 0.0], @@ -51,10 +55,14 @@ def get_dict(): ['BR-RF-DLLRF-01:ILK:RFIN14:S', 0, 0.0], ['BR-RF-DLLRF-01:ILK:RFIN15:S', 0, 0.0], ['BR-RF-DLLRF-01:ILK:BEAM:TRIP:S', 0, 0.0], - ['BR-RF-DLLRF-01:SWITCHES:S', 0, 0.0], # End switches logic - ['BR-RF-DLLRF-01:TRIPINVERT:S', 0, 0.0], # Beam trip logic - ['BR-RF-DLLRF-01:VACINVERT:S', 0, 0.0], # Vacuum sensor logic - ['BR-RF-DLLRF-01:LIMIT:REVSSA1:S', 0, 0.0], # Pwr intlck threshold [mV] + # End switches logic + ['BR-RF-DLLRF-01:SWITCHES:S', 0, 0.0], + # Beam trip logic + ['BR-RF-DLLRF-01:TRIPINVERT:S', 0, 0.0], + # Vacuum sensor logic + ['BR-RF-DLLRF-01:VACINVERT:S', 0, 0.0], + # Pwr interlock threshold + ['BR-RF-DLLRF-01:LIMIT:REVSSA1:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:LIMIT:REVSSA2:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:LIMIT:REVSSA3:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:LIMIT:REVSSA4:S', 0, 0.0], # [mV] @@ -71,8 +79,10 @@ def get_dict(): ['BR-RF-DLLRF-01:LIMIT:RFIN13:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:LIMIT:RFIN14:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:LIMIT:RFIN15:S', 0, 0.0], # [mV] - ['BR-RF-DLLRF-01:ILK:DELAY:S', 0, 0.0], # [μs] Interlock delay - ['BR-RF-DLLRF-01:mV:AL:REF-SP.DRVH', 0, 0.0], # Settings PVs values lims + # Interlock delay + ['BR-RF-DLLRF-01:ILK:DELAY:S', 0, 0.0], # [μs] + # Settings PVs values lims + ['BR-RF-DLLRF-01:mV:AL:REF-SP.DRVH', 0, 0.0], ['BR-RF-DLLRF-01:mV:AL:REF-SP.DRVL', 0, 0.0], ['BR-RF-DLLRF-01:mV:RAMP:AMP:TOP-SP.DRVH', 0, 0.0], ['BR-RF-DLLRF-01:mV:RAMP:AMP:TOP-SP.DRVL', 0, 0.0], @@ -80,13 +90,18 @@ def get_dict(): ['BR-RF-DLLRF-01:mV:RAMP:AMP:BOT-SP.DRVL', 0, 0.0], ['BR-RF-DLLRF-01:OLGAIN:S.DRVH', 0, 0.0], ['BR-RF-DLLRF-01:OLGAIN:S.DRVL', 0, 0.0], - ['BR-RF-DLLRF-01:SL:KP:S.DRVH', 0, 0.0], # kp limit high - ['BR-RF-DLLRF-01:SL:KP:S.DRVL', 0, 0.0], # kp limit low - ['BO-05D:VA-CCG-RFC:FastRelay-SP', 0, 0.0], # Pressure threshold - ['BR-RF-DLLRF-01:AUTOCOND:S', 0, 0.0], # Pressure Lock power increase + # kp limit high + ['BR-RF-DLLRF-01:SL:KP:S.DRVH', 0, 0.0], + # kp limit low + ['BR-RF-DLLRF-01:SL:KP:S.DRVL', 0, 0.0], + # Pressure threshold + ['BO-05D:VA-CCG-RFC:FastRelay-SP', 0, 0.0], + # Pressure Lock power increase + ['BR-RF-DLLRF-01:AUTOCOND:S', 0, 0.0], ['BR-RF-DLLRF-01:EPS:S', 0, 0.0], ['BR-RF-DLLRF-01:FIM:S', 0, 0.0], - ['BR-RF-DLLRF-01:PHSH:CAV:S', 0, 0.0], # [°] # ADC Phase and Gain + # ADC Phase and Gain + ['BR-RF-DLLRF-01:PHSH:CAV:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:PHSH:FWDCAV:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:PHSH:FWDSSA1:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:PHSH:FWDSSA2:S', 0, 0.0], # [°] @@ -97,7 +112,8 @@ def get_dict(): ['BR-RF-DLLRF-01:GAIN:FWDSSA2:S', 0, 0.0], ['BR-RF-DLLRF-01:GAIN:FWDSSA3:S', 0, 0.0], ['BR-RF-DLLRF-01:GAIN:FWDSSA4:S', 0, 0.0], - ['BR-RF-DLLRF-01:PHSH:SSA1:S', 0, 0.0], # [°] # DAC Phse and Gain + # DAC Phse and Gain + ['BR-RF-DLLRF-01:PHSH:SSA1:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:PHSH:SSA2:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:PHSH:SSA3:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:PHSH:SSA4:S', 0, 0.0], # [°] @@ -105,7 +121,8 @@ def get_dict(): ['BR-RF-DLLRF-01:GAIN:SSA2:S', 0, 0.0], ['BR-RF-DLLRF-01:GAIN:SSA3:S', 0, 0.0], ['BR-RF-DLLRF-01:GAIN:SSA4:S', 0, 0.0], - ['BR-RF-DLLRF-01:SL:KP:S', 0, 0.0], # Loops parameters + # Loops parameters + ['BR-RF-DLLRF-01:SL:KP:S', 0, 0.0], ['BR-RF-DLLRF-01:SL:KI:S', 0, 0.0], ['BR-RF-DLLRF-01:SL:PILIMIT:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:SL:SEL:S', 0, 0.0], @@ -119,12 +136,18 @@ def get_dict(): ['BR-RF-DLLRF-01:PL:KP:S', 0, 0.0], ['BR-RF-DLLRF-01:PL:KI:S', 0, 0.0], ['BR-RF-DLLRF-01:PL:SEL:S', 0, 0.0], - ['BR-RF-DLLRF-01:MODE:S', 0, 0.0], # Loop mode - ['BR-RF-DLLRF-01:FWMIN:AMPPHS:S', 0, 0.0], # [mV] # Min forward power - ['BR-RF-DLLRF-01:mV:AMPREF:MIN:S', 0, 0.0], # [mV] # Min amplitude ref. - ['BR-RF-DLLRF-01:PHSREF:MIN:S', 0, 0.0], # [°] # Min phase ref. - ['BR-RF-DLLRF-01:OLGAIN:S', 0, 0.0], # Open loop gain - ['BR-RF-DLLRF-01:TUNE:POS:S', 0, 0.0], # Tuning loop config + # Loop mode + ['BR-RF-DLLRF-01:MODE:S', 0, 0.0], + # Min forward power + ['BR-RF-DLLRF-01:FWMIN:AMPPHS:S', 0, 0.0], # [mV] + # Min amplitude ref + ['BR-RF-DLLRF-01:mV:AMPREF:MIN:S', 0, 0.0], # [mV] + # Min phase ref + ['BR-RF-DLLRF-01:PHSREF:MIN:S', 0, 0.0], # [°] + # Open loop gain + ['BR-RF-DLLRF-01:OLGAIN:S', 0, 0.0], + # Tuning loop config + ['BR-RF-DLLRF-01:TUNE:POS:S', 0, 0.0], ['BR-RF-DLLRF-01:TUNE:FWMIN:S', 0, 0.0], # [mV] ['BR-RF-DLLRF-01:TUNE:MARGIN:HI:S', 0, 0.0], # [°] ['BR-RF-DLLRF-01:TUNE:MARGIN:LO:S', 0, 0.0], # [°] @@ -134,13 +157,16 @@ def get_dict(): ['BR-RF-DLLRF-01:TUNE:FILT:S', 0, 0.0], ['BR-RF-DLLRF-01:TUNE:TRIG:S', 0, 0.0], ['BR-RF-DLLRF-01:TUNE:TOPRAMP:S', 0, 0.0], - ['BR-RF-DLLRF-01:FF:POS:S', 0, 0.0], # Field Flatness loop config + # Field Flatness loop config + ['BR-RF-DLLRF-01:FF:POS:S', 0, 0.0], ['BR-RF-DLLRF-01:FF:DEADBAND:S', 0, 0.0], # [%] ['BR-RF-DLLRF-01:FF:GAIN:CELL2:S', 0, 0.0], ['BR-RF-DLLRF-01:FF:GAIN:CELL4:S', 0, 0.0], - ['BR-RF-DLLRF-01:freq:cond:S', 0, 0.0], # [Hz] # Pulsed mode config + # Pulsed mode config + ['BR-RF-DLLRF-01:freq:cond:S', 0, 0.0], # [Hz] ['BR-RF-DLLRF-01:freq:duty:S', 0, 0.0], # [%] - ['BR-RF-DLLRF-01:RmpTs1-SP', 0, 0.0], # [ms] # Ramp mode config + # Ramp mode config + ['BR-RF-DLLRF-01:RmpTs1-SP', 0, 0.0], # [ms] ['BR-RF-DLLRF-01:RmpTs2-SP', 0, 0.0], # [ms] ['BR-RF-DLLRF-01:RmpTs3-SP', 0, 0.0], # [ms] ['BR-RF-DLLRF-01:RmpTs4-SP', 0, 0.0], # [ms] @@ -152,16 +178,18 @@ def get_dict(): ['BR-RF-DLLRF-01:DisableRampDown:S', 0, 0.0], ['BR-RF-DLLRF-01:FDL:FrameQty-SP', 0, 0.0], ['BR-RF-DLLRF-01:FDL:REARM', 0, 0.0], - ['RA-RaBO01:RF-CavPlDrivers:DrEnbl-Sel', 0, 0.0], # Enable plungers step - # motor drivers - ['BR-RF-DLLRF-01:PHSH:ADC:S', 0, 0.0], # Enable ADC phase and gain - ['BR-RF-DLLRF-01:PHSH:DAC:S', 0, 0.0], # Enable DAC phase and gain + # Enable plungers step motor drivers + ['RA-RaBO01:RF-CavPlDrivers:DrEnbl-Sel', 0, 0.0], + # Enable ADC phase and gain + ['BR-RF-DLLRF-01:PHSH:ADC:S', 0, 0.0], + # Enable DAC phase and gain + ['BR-RF-DLLRF-01:PHSH:DAC:S', 0, 0.0], ] _pvs_bo_rfssa = [ - ['RA-ToBO:OffsetConfig:UpperIncidentPower', 0, 0.0], # [dB] Offsets - # SSA Tower + # SSA tower offsets + ['RA-ToBO:OffsetConfig:UpperIncidentPower', 0, 0.0], # [dB] ['RA-ToBO:OffsetConfig:UpperReflectedPower', 0, 0.0], # [dB] ['RA-ToBO:OffsetConfig:LowerIncidentPower', 0, 0.0], # [dB] ['RA-ToBO:OffsetConfig:LowerReflectedPower', 0, 0.0], # [dB] @@ -169,8 +197,8 @@ def get_dict(): ['RA-ToBO:OffsetConfig:InputReflectedPower', 0, 0.0], # [dB] ['RA-ToBO:OffsetConfig:OutputIncidentPower', 0, 0.0], # [dB] ['RA-ToBO:OffsetConfig:OutputReflectedPower', 0, 0.0], # [dB] - ['RA-ToBO:AlarmConfig:GeneralPowerLimHiHi', 0, 0.0], # [dBm] Alarms limits - # power SSA tower + # SSA tower pwr alarm limits + ['RA-ToBO:AlarmConfig:GeneralPowerLimHiHi', 0, 0.0], # [dBm] ['RA-ToBO:AlarmConfig:GeneralPowerLimHigh', 0, 0.0], # [dBm] ['RA-ToBO:AlarmConfig:GeneralPowerLimLow', 0, 0.0], # [dBm] ['RA-ToBO:AlarmConfig:GeneralPowerLimLoLo', 0, 0.0], # [dBm] @@ -178,8 +206,8 @@ def get_dict(): ['RA-ToBO:AlarmConfig:InnerPowerLimHigh', 0, 0.0], # [dBm] ['RA-ToBO:AlarmConfig:InnerPowerLimLow', 0, 0.0], # [dBm] ['RA-ToBO:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], # [dBm] - ['RA-ToBO:AlarmConfig:CurrentLimHiHi', 0, 0.0], # [A] Alarms limits - # currents SSA tower + # SSA tower current alarm limits + ['RA-ToBO:AlarmConfig:CurrentLimHiHi', 0, 0.0], # [A] ['RA-ToBO:AlarmConfig:CurrentLimHigh', 0, 0.0], # [A] ['RA-ToBO:AlarmConfig:CurrentLimLow', 0, 0.0], # [A] ['RA-ToBO:AlarmConfig:CurrentLimLoLo', 0, 0.0], # [A] @@ -187,7 +215,8 @@ def get_dict(): _pvs_bo_rfcal = [ - ['BR-RF-DLLRF-01:CAV:Const:OFS:S', 0, 0.0], # Offsets and conv coeffs + # Offsets and conv coeffs + ['BR-RF-DLLRF-01:CAV:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:CAV:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:CAV:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:CAV:Const:Raw-U:C2:S', 0, 0.0], @@ -198,7 +227,7 @@ def get_dict(): ['BR-RF-DLLRF-01:CAV:Const:U-Raw:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:CAV:Const:U-Raw:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:CAV:Const:U-Raw:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:FWDCAV:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:FWDCAV:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:FWDCAV:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCAV:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCAV:Const:Raw-U:C2:S', 0, 0.0], @@ -209,19 +238,19 @@ def get_dict(): ['BR-RF-DLLRF-01:FWDCAV:Const:U-Raw:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCAV:Const:U-Raw:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCAV:Const:U-Raw:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:REVCAV:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:REVCAV:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:REVCAV:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:REVCAV:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:REVCAV:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:REVCAV:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:REVCAV:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:MO:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:MO:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:MO:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:MO:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:MO:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:MO:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:MO:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:FWDSSA1:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:FWDSSA1:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:FWDSSA1:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDSSA1:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDSSA1:Const:Raw-U:C2:S', 0, 0.0], @@ -232,61 +261,61 @@ def get_dict(): ['BR-RF-DLLRF-01:FWDSSA1:Const:U-Raw:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDSSA1:Const:U-Raw:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDSSA1:Const:U-Raw:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:REVSSA1:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:REVSSA1:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:REVSSA1:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:REVSSA1:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:REVSSA1:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:REVSSA1:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:REVSSA1:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:CELL2:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:CELL2:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:CELL2:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL2:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL2:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL2:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL2:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:CELL4:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:CELL4:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:CELL4:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL4:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL4:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL4:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL4:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:CELL1:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:CELL1:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:CELL1:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL1:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL1:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL1:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL1:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:CELL5:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:CELL5:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:CELL5:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL5:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL5:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL5:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:CELL5:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:INPRE:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:INPRE:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:INPRE:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:INPRE:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:INPRE:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:INPRE:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:INPRE:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:FWDPRE:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:FWDPRE:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:FWDPRE:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDPRE:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDPRE:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDPRE:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDPRE:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:REVPRE:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:REVPRE:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:REVPRE:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:REVPRE:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:REVPRE:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:REVPRE:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:REVPRE:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:FWDCIRC:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:FWDCIRC:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:FWDCIRC:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCIRC:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCIRC:Const:Raw-U:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCIRC:Const:Raw-U:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:FWDCIRC:Const:Raw-U:C4:S', 0, 0.0], - ['BR-RF-DLLRF-01:REVCIRC:Const:OFS:S', 0, 0.0], + ['BR-RF-DLLRF-01:REVCIRC:Const:OFS:S', 0, 0.0], # [dB] ['BR-RF-DLLRF-01:REVCIRC:Const:Raw-U:C0:S', 0, 0.0], ['BR-RF-DLLRF-01:REVCIRC:Const:Raw-U:C1:S', 0, 0.0], ['BR-RF-DLLRF-01:REVCIRC:Const:Raw-U:C2:S', 0, 0.0], @@ -307,23 +336,52 @@ def get_dict(): ['BR-RF-DLLRF-01:OLG:FWDSSA1:Const:C2:S', 0, 0.0], ['BR-RF-DLLRF-01:OLG:FWDSSA1:Const:C3:S', 0, 0.0], ['BR-RF-DLLRF-01:OLG:FWDSSA1:Const:C4:S', 0, 0.0], - ['RA-RaBO01:RF-LLRF:AmpVCav2HwCoeff0-SP', 0, 0.0], # Conv coeffs for - # Voltage Gap calc + # Conv coeffs for gap voltage calc + ['RA-RaBO01:RF-LLRF:AmpVCav2HwCoeff0-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:AmpVCav2HwCoeff1-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:AmpVCav2HwCoeff2-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:AmpVCav2HwCoeff3-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:AmpVCav2HwCoeff4-SP', 0, 0.0], - ['BO-05D:RF-P5Cav:Rsh-SP', 0, 0.0], # Cavity Shunt impedance + # Cavity Shunt impedance + ['BO-05D:RF-P5Cav:Rsh-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:Hw2AmpVCavCoeff0-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:Hw2AmpVCavCoeff1-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:Hw2AmpVCavCoeff2-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:Hw2AmpVCavCoeff3-SP', 0, 0.0], ['RA-RaBO01:RF-LLRF:Hw2AmpVCavCoeff4-SP', 0, 0.0], + # CalSys Offsets + ['RA-RaBO01:RF-RFCalSys:OFSdB1-Mon', 0, 0.0], # [dB] + ['RA-RaBO01:RF-RFCalSys:OFSdB2-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB3-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB4-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB5-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB6-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB7-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB8-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB9-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB10-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB11-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB12-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB13-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB14-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB15-Mon', 0, 0.0], + ['RA-RaBO01:RF-RFCalSys:OFSdB16-Mon', 0, 0.0], + ] + + +_pvs_bo_pow_sensor = [ + #Keysight U2021xa Power Sensor config + ['RA-RF:PowerSensor1:GainOffsetStat-Sel', 0, 0.0], + ['RA-RF:PowerSensor1:GainOffset-SP', 0, 0.0], + ['RA-RF:PowerSensor1:Egu-SP', 0, 0.0], + ['RA-RF:PowerSensor1:TracTime-SP', 0, 0.0], + ['RA-RF:PowerSensor1:Freq-SP', 0, 0.0], ] _pvs_si_llrf = [ - ['SR-RF-DLLRF-01:ILK:REVSSA1:S', 0, 0.0], # Interlock disable + # Interlock disable + ['SR-RF-DLLRF-01:ILK:REVSSA1:S', 0, 0.0], ['SR-RF-DLLRF-01:ILK:REVSSA2:S', 0, 0.0], ['SR-RF-DLLRF-01:ILK:REVSSA3:S', 0, 0.0], ['SR-RF-DLLRF-01:ILK:REVSSA4:S', 0, 0.0], @@ -350,10 +408,14 @@ def get_dict(): ['SR-RF-DLLRF-01:ILK:RFIN14:S', 0, 0.0], ['SR-RF-DLLRF-01:ILK:RFIN15:S', 0, 0.0], ['SR-RF-DLLRF-01:ILK:BEAM:TRIP:S', 0, 0.0], - ['SR-RF-DLLRF-01:SWITCHES:S', 0, 0.0], # End switches logic - ['SR-RF-DLLRF-01:TRIPINVERT:S', 0, 0.0], # Beam trip logic - ['SR-RF-DLLRF-01:VACINVERT:S', 0, 0.0], # Vacuum sensor logic - ['SR-RF-DLLRF-01:LIMIT:REVSSA1:S', 0, 0.0], # [mV] Pwr intlck threshold + # End switches logic + ['SR-RF-DLLRF-01:SWITCHES:S', 0, 0.0], + # Beam trip logic + ['SR-RF-DLLRF-01:TRIPINVERT:S', 0, 0.0], + # Vacuum sensor logic + ['SR-RF-DLLRF-01:VACINVERT:S', 0, 0.0], + # Pwr interlock threshold + ['SR-RF-DLLRF-01:LIMIT:REVSSA1:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:LIMIT:REVSSA2:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:LIMIT:REVSSA3:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:LIMIT:REVSSA4:S', 0, 0.0], # [mV] @@ -370,17 +432,21 @@ def get_dict(): ['SR-RF-DLLRF-01:LIMIT:RFIN13:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:LIMIT:RFIN14:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:LIMIT:RFIN15:S', 0, 0.0], # [mV] - ['SR-RF-DLLRF-01:ILK:DELAY:S', 0, 0.0], # [μs] # Interlock delay - ['SR-RF-DLLRF-01:mV:AL:REF-SP.DRVH', 0, 0.0], # [mV] Set PVs value lims + # Interlock delay + ['SR-RF-DLLRF-01:ILK:DELAY:S', 0, 0.0], # [μs] + # Set PVs value lims + ['SR-RF-DLLRF-01:mV:AL:REF-SP.DRVH', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:mV:AL:REF-SP.DRVL', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:OLGAIN:S.DRVH', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:OLGAIN:S.DRVL', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:SL:KP:S.DRVH', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:SL:KP:S.DRVL', 0, 0.0], # [mV] - ['SR-RF-DLLRF-01:AUTOCOND:S', 0, 0.0], # Pressure Lock power increase + # Pressure Lock power increase + ['SR-RF-DLLRF-01:AUTOCOND:S', 0, 0.0], ['SR-RF-DLLRF-01:EPS:S', 0, 0.0], ['SR-RF-DLLRF-01:FIM:S', 0, 0.0], - ['SR-RF-DLLRF-01:PHSH:CAV:S', 0, 0.0], # [°] ADC Phase and Gain + # ADC Phase and Gain + ['SR-RF-DLLRF-01:PHSH:CAV:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:PHSH:FWDCAV:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:PHSH:FWDSSA1:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:PHSH:FWDSSA2:S', 0, 0.0], # [°] @@ -391,7 +457,8 @@ def get_dict(): ['SR-RF-DLLRF-01:GAIN:FWDSSA2:S', 0, 0.0], ['SR-RF-DLLRF-01:GAIN:FWDSSA3:S', 0, 0.0], ['SR-RF-DLLRF-01:GAIN:FWDSSA4:S', 0, 0.0], - ['SR-RF-DLLRF-01:PHSH:SSA1:S', 0, 0.0], # [°] DAC Phse and Gain + # DAC Phse and Gain + ['SR-RF-DLLRF-01:PHSH:SSA1:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:PHSH:SSA2:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:PHSH:SSA3:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:PHSH:SSA4:S', 0, 0.0], # [°] @@ -399,7 +466,8 @@ def get_dict(): ['SR-RF-DLLRF-01:GAIN:SSA2:S', 0, 0.0], ['SR-RF-DLLRF-01:GAIN:SSA3:S', 0, 0.0], ['SR-RF-DLLRF-01:GAIN:SSA4:S', 0, 0.0], - ['SR-RF-DLLRF-01:SL:KP:S', 0, 0.0], # Loops parameters + # Loops parameters + ['SR-RF-DLLRF-01:SL:KP:S', 0, 0.0], ['SR-RF-DLLRF-01:SL:KI:S', 0, 0.0], ['SR-RF-DLLRF-01:SL:PILIMIT:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:SL:SEL:S', 0, 0.0], @@ -413,13 +481,20 @@ def get_dict(): ['SR-RF-DLLRF-01:PL:KP:S', 0, 0.0], ['SR-RF-DLLRF-01:PL:KI:S', 0, 0.0], ['SR-RF-DLLRF-01:PL:SEL:S', 0, 0.0], - ['SR-RF-DLLRF-01:MODE:S', 0, 0.0], # Loop mode - ['SR-RF-DLLRF-01:FWMIN:AMPPHS:S', 0, 0.0], # [mV] Min forward power - ['SR-RF-DLLRF-01:mV:AMPREF:MIN:S', 0, 0.0], # [mV] Min amplitude reference - ['SR-RF-DLLRF-01:PHSREF:MIN:S', 0, 0.0], # [°] Min phase reference - ['SR-RF-DLLRF-01:OLGAIN:S', 0, 0.0], # Open loop gain - ['SR-RF-DLLRF-01:PL:REF:S', 0, 0.0], # [°] Phase ref - ['SR-RF-DLLRF-01:TUNE:POS:S', 0, 0.0], # Tuning loop config + # Loop mode + ['SR-RF-DLLRF-01:MODE:S', 0, 0.0], + # Min forward power + ['SR-RF-DLLRF-01:FWMIN:AMPPHS:S', 0, 0.0], # [mV] + # Min amplitude reference + ['SR-RF-DLLRF-01:mV:AMPREF:MIN:S', 0, 0.0], # [mV] + # Min phase reference + ['SR-RF-DLLRF-01:PHSREF:MIN:S', 0, 0.0], # [°] + # Open loop gain + ['SR-RF-DLLRF-01:OLGAIN:S', 0, 0.0], + # Phase ref + ['SR-RF-DLLRF-01:PL:REF:S', 0, 0.0], # [°] + # Tuning loop config + ['SR-RF-DLLRF-01:TUNE:POS:S', 0, 0.0], ['SR-RF-DLLRF-01:TUNE:FWMIN:S', 0, 0.0], # [mV] ['SR-RF-DLLRF-01:TUNE:MARGIN:HI:S', 0, 0.0], # [°] ['SR-RF-DLLRF-01:TUNE:MARGIN:LO:S', 0, 0.0], # [°] @@ -429,12 +504,14 @@ def get_dict(): ['SR-RF-DLLRF-01:TUNE:FILT:S', 0, 0.0], ['SR-RF-DLLRF-01:TUNE:TRIG:S', 0, 0.0], ['SR-RF-DLLRF-01:TUNE:TOPRAMP:S', 0, 0.0], - ['SR-RF-DLLRF-01:FF:POS:S', 0, 0.0], # Field Flatness loop config + # Field Flatness loop config + ['SR-RF-DLLRF-01:FF:POS:S', 0, 0.0], ['SR-RF-DLLRF-01:FF:DEADBAND:S', 0, 0.0], # [%] ['SR-RF-DLLRF-01:FF:GAIN:CELL2:S', 0, 0.0], ['SR-RF-DLLRF-01:FF:GAIN:CELL4:S', 0, 0.0], ['SR-RF-DLLRF-01:COND:DC:S', 0, 0.0], # [%] - ['SR-RF-DLLRF-01:RmpTs1-SP', 0, 0.0], # [ms] Ramp mode config + # Ramp mode config + ['SR-RF-DLLRF-01:RmpTs1-SP', 0, 0.0], # [ms] ['SR-RF-DLLRF-01:RmpTs2-SP', 0, 0.0], # [ms] ['SR-RF-DLLRF-01:RmpTs3-SP', 0, 0.0], # [ms] ['SR-RF-DLLRF-01:RmpTs4-SP', 0, 0.0], # [ms] @@ -446,61 +523,101 @@ def get_dict(): ['SR-RF-DLLRF-01:DisableRampDown:S', 0, 0.0], ['SR-RF-DLLRF-01:FDL:FrameQty-SP', 0, 0.0], ['SR-RF-DLLRF-01:FDL:REARM', 0, 0.0], - ['SR-RF-DLLRF-01:PHSH:ADC:S', 0, 0.0], # Enable ADC phase and gain - ['SR-RF-DLLRF-01:PHSH:DAC:S', 0, 0.0], # Enable DAC phase and gain + # Enable ADC phase and gain + ['SR-RF-DLLRF-01:PHSH:ADC:S', 0, 0.0], + # Enable DAC phase and gain + ['SR-RF-DLLRF-01:PHSH:DAC:S', 0, 0.0], ] _pvs_si_rfssa = [ - ['RA-ToSIA01:OffsetConfig:UpperIncidentPower', 0, 0.0], # Offsets SSA Twr1 - ['RA-ToSIA01:OffsetConfig:UpperReflectedPower', 0, 0.0], - ['RA-ToSIA01:OffsetConfig:LowerIncidentPower', 0, 0.0], - ['RA-ToSIA01:OffsetConfig:LowerReflectedPower', 0, 0.0], - ['RA-ToSIA01:OffsetConfig:InputIncidentPower', 0, 0.0], - ['RA-ToSIA01:OffsetConfig:InputReflectedPower', 0, 0.0], - ['RA-ToSIA01:OffsetConfig:OutputIncidentPower', 0, 0.0], - ['RA-ToSIA01:OffsetConfig:OutputReflectedPower', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:GeneralPowerLimHiHi', 0, 0.0], # Alarms lims pwr - # SSA tower 1 - ['RA-ToSIA01:AlarmConfig:GeneralPowerLimHigh', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:GeneralPowerLimLow', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:GeneralPowerLimLoLo', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:InnerPowerLimHiHi', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:InnerPowerLimHigh', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:InnerPowerLimLow', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:CurrentLimHiHi', 0, 0.0], # Alarms lims currents - # SSA tower 1 - ['RA-ToSIA01:AlarmConfig:CurrentLimHigh', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:CurrentLimLow', 0, 0.0], - ['RA-ToSIA01:AlarmConfig:CurrentLimLoLo', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:UpperIncidentPower', 0, 0.0], # Offsets SSA Twr2 - ['RA-ToSIA02:OffsetConfig:UpperReflectedPower', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:LowerIncidentPower', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:LowerReflectedPower', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:InputIncidentPower', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:InputReflectedPower', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:OutputIncidentPower', 0, 0.0], - ['RA-ToSIA02:OffsetConfig:OutputReflectedPower', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:GeneralPowerLimHiHi', 0, 0.0], # Alarms lims pwr - # SSA tower 2 - ['RA-ToSIA02:AlarmConfig:GeneralPowerLimHigh', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:GeneralPowerLimLow', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:GeneralPowerLimLoLo', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:InnerPowerLimHiHi', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:InnerPowerLimHigh', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:InnerPowerLimLow', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:CurrentLimHiHi', 0, 0.0], # Alarms lims currents - # SSA tower 2 - ['RA-ToSIA02:AlarmConfig:CurrentLimHigh', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:CurrentLimLow', 0, 0.0], - ['RA-ToSIA02:AlarmConfig:CurrentLimLoLo', 0, 0.0], + # NOTE: Alarms and offset of SSA towers 1 & 2 temporaly removed + # SSA tower 1 offsets + # ['RA-ToSIA01:OffsetConfig:UpperIncidentPower', 0, 0.0], + # ['RA-ToSIA01:OffsetConfig:UpperReflectedPower', 0, 0.0], + # ['RA-ToSIA01:OffsetConfig:LowerIncidentPower', 0, 0.0], + # ['RA-ToSIA01:OffsetConfig:LowerReflectedPower', 0, 0.0], + # SSA tower 1 pwr alarm limits + # ['RA-ToSIA01:AlarmConfig:InnerPowerLimHiHi', 0, 0.0], + # ['RA-ToSIA01:AlarmConfig:InnerPowerLimHigh', 0, 0.0], + # ['RA-ToSIA01:AlarmConfig:InnerPowerLimLow', 0, 0.0], + # ['RA-ToSIA01:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], + # SSA tower 1 current alarm limits + # ['RA-ToSIA01:AlarmConfig:CurrentLimHiHi', 0, 0.0], + # ['RA-ToSIA01:AlarmConfig:CurrentLimHigh', 0, 0.0], + # ['RA-ToSIA01:AlarmConfig:CurrentLimLow', 0, 0.0], + # ['RA-ToSIA01:AlarmConfig:CurrentLimLoLo', 0, 0.0], + # SSA tower 2 offsets + # ['RA-ToSIA02:OffsetConfig:UpperIncidentPower', 0, 0.0], + # ['RA-ToSIA02:OffsetConfig:UpperReflectedPower', 0, 0.0], + # ['RA-ToSIA02:OffsetConfig:LowerIncidentPower', 0, 0.0], + # ['RA-ToSIA02:OffsetConfig:LowerReflectedPower', 0, 0.0], + # SSA tower 2 pwr alarm limits + # ['RA-ToSIA02:AlarmConfig:InnerPowerLimHiHi', 0, 0.0], + # ['RA-ToSIA02:AlarmConfig:InnerPowerLimHigh', 0, 0.0], + # ['RA-ToSIA02:AlarmConfig:InnerPowerLimLow', 0, 0.0], + # ['RA-ToSIA02:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], + # SSA tower 2 current alarm limits + # ['RA-ToSIA02:AlarmConfig:CurrentLimHiHi', 0, 0.0], + # ['RA-ToSIA02:AlarmConfig:CurrentLimHigh', 0, 0.0], + # ['RA-ToSIA02:AlarmConfig:CurrentLimLow', 0, 0.0], + # ['RA-ToSIA02:AlarmConfig:CurrentLimLoLo', 0, 0.0], + # SSA tower 3 offsets + ['RA-ToSIA03:OffsetConfig:UpperIncidentPower', 0, 0.0], + ['RA-ToSIA03:OffsetConfig:UpperReflectedPower', 0, 0.0], + ['RA-ToSIA03:OffsetConfig:LowerIncidentPower', 0, 0.0], + ['RA-ToSIA03:OffsetConfig:LowerReflectedPower', 0, 0.0], + # SSA tower 3 pwr alarm limits + ['RA-ToSIA03:AlarmConfig:InnerPowerLimHiHi', 0, 0.0], + ['RA-ToSIA03:AlarmConfig:InnerPowerLimHigh', 0, 0.0], + ['RA-ToSIA03:AlarmConfig:InnerPowerLimLow', 0, 0.0], + ['RA-ToSIA03:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], + # SSA tower 3 current alarm limits + ['RA-ToSIA03:AlarmConfig:CurrentLimHiHi', 0, 0.0], + ['RA-ToSIA03:AlarmConfig:CurrentLimHigh', 0, 0.0], + ['RA-ToSIA03:AlarmConfig:CurrentLimLow', 0, 0.0], + ['RA-ToSIA03:AlarmConfig:CurrentLimLoLo', 0, 0.0], + # SSA tower 4 offsets + ['RA-ToSIA04:OffsetConfig:UpperIncidentPower', 0, 0.0], + ['RA-ToSIA04:OffsetConfig:UpperReflectedPower', 0, 0.0], + ['RA-ToSIA04:OffsetConfig:LowerIncidentPower', 0, 0.0], + ['RA-ToSIA04:OffsetConfig:LowerReflectedPower', 0, 0.0], + # SSA tower 4 pwr alarm limits + ['RA-ToSIA04:AlarmConfig:InnerPowerLimHiHi', 0, 0.0], + ['RA-ToSIA04:AlarmConfig:InnerPowerLimHigh', 0, 0.0], + ['RA-ToSIA04:AlarmConfig:InnerPowerLimLow', 0, 0.0], + ['RA-ToSIA04:AlarmConfig:InnerPowerLimLoLo', 0, 0.0], + # SSA tower 4 current alarm limits + ['RA-ToSIA04:AlarmConfig:CurrentLimHiHi', 0, 0.0], + ['RA-ToSIA04:AlarmConfig:CurrentLimHigh', 0, 0.0], + ['RA-ToSIA04:AlarmConfig:CurrentLimLow', 0, 0.0], + ['RA-ToSIA04:AlarmConfig:CurrentLimLoLo', 0, 0.0], + # SSA1 Pwr Cal Coeff + # ['RA-ToSIA01:RF-SSAmpTower:Hw2PwrFwdInCoeff-Cte', az, 0.0], + # ['RA-ToSIA01:RF-SSAmpTower:Hw2PwrRevInCoeff-Cte', az, 0.0], + # ['RA-ToSIA01:RF-SSAmpTower:Hw2PwrFwdOutCoeff-Cte', az, 0.0], + # ['RA-ToSIA01:RF-SSAmpTower:Hw2PwrRevOutCoeff-Cte', az, 0.0], + # SSA2 Pwr Cal Coeff + # ['RA-ToSIA02:RF-SSAmpTower:Hw2PwrFwdInCoeff-Cte', az, 0.0], + # ['RA-ToSIA02:RF-SSAmpTower:Hw2PwrRevInCoeff-Cte', az, 0.0], + # ['RA-ToSIA02:RF-SSAmpTower:Hw2PwrFwdOutCoeff-Cte', az, 0.0], + # ['RA-ToSIA02:RF-SSAmpTower:Hw2PwrRevOutCoeff-Cte', az, 0.0], + # SSA3 Pwr Cal Coeff + ['RA-ToSIA03:RF-SSAmpTower:Hw2PwrFwdInCoeff-Cte', az, 0.0], + ['RA-ToSIA03:RF-SSAmpTower:Hw2PwrRevInCoeff-Cte', az, 0.0], + ['RA-ToSIA03:RF-SSAmpTower:Hw2PwrFwdOutCoeff-Cte', az, 0.0], + ['RA-ToSIA03:RF-SSAmpTower:Hw2PwrRevOutCoeff-Cte', az, 0.0], + # SSA4 Pwr Cal Coeff + ['RA-ToSIA04:RF-SSAmpTower:Hw2PwrFwdInCoeff-Cte', az, 0.0], + ['RA-ToSIA04:RF-SSAmpTower:Hw2PwrRevInCoeff-Cte', az, 0.0], + ['RA-ToSIA04:RF-SSAmpTower:Hw2PwrFwdOutCoeff-Cte', az, 0.0], + ['RA-ToSIA04:RF-SSAmpTower:Hw2PwrRevOutCoeff-Cte', az, 0.0], ] _pvs_si_rfcav = [ - ['SI-02SB:RF-P7Cav:Disc1FlwRt-Mon', 0, 0.0], # [L/h] CavP7 water flowrate + # CavP7 water flow rate + ['SI-02SB:RF-P7Cav:Disc1FlwRt-Mon', 0, 0.0], # [L/h] ['SI-02SB:RF-P7Cav:Cell1FlwRt-Mon', 0, 0.0], # [L/h] ['SI-02SB:RF-P7Cav:Disc2FlwRt-Mon', 0, 0.0], # [L/h] ['SI-02SB:RF-P7Cav:Cell2FlwRt-Mon', 0, 0.0], # [L/h] @@ -519,7 +636,8 @@ def get_dict(): _pvs_si_rfcal = [ - ['SR-RF-DLLRF-01:CAV:Const:OFS:S', 0, 0.0], # [dB] Offsets and conv coeffs + # Offsets and conv coeffs + ['SR-RF-DLLRF-01:CAV:Const:OFS:S', 0, 0.0], # [dB] ['SR-RF-DLLRF-01:CAV:Const:Raw-U:C0:S', 0, 0.0], ['SR-RF-DLLRF-01:CAV:Const:Raw-U:C1:S', 0, 0.0], ['SR-RF-DLLRF-01:CAV:Const:Raw-U:C2:S', 0, 0.0], @@ -639,19 +757,38 @@ def get_dict(): ['SR-RF-DLLRF-01:OLG:FWDSSA1:Const:C2:S', 0, 0.0], ['SR-RF-DLLRF-01:OLG:FWDSSA1:Const:C3:S', 0, 0.0], ['SR-RF-DLLRF-01:OLG:FWDSSA1:Const:C4:S', 0, 0.0], - ['RA-RaSIA01:RF-LLRF:AmpVCav2HwCoeff0-SP', 0, 0.0], # Conv coeffs for - # Voltage Gap calc + # Conv coeffs for gap voltage calc + ['RA-RaSIA01:RF-LLRF:AmpVCav2HwCoeff0-SP', 0, 0.0], ['RA-RaSIA01:RF-LLRF:AmpVCav2HwCoeff1-SP', 0, 0.0], ['RA-RaSIA01:RF-LLRF:AmpVCav2HwCoeff2-SP', 0, 0.0], ['RA-RaSIA01:RF-LLRF:AmpVCav2HwCoeff3-SP', 0, 0.0], ['RA-RaSIA01:RF-LLRF:AmpVCav2HwCoeff4-SP', 0, 0.0], - ['SI-02SB:RF-P7Cav:Rsh-SP', 0, 0.0], # [Ohm] # Cavity Shunt impedance + # Cavity Shunt impedance + ['SI-02SB:RF-P7Cav:Rsh-SP', 0, 0.0], # [Ohm] + # CalSys Offsets + ['RA-RaSIA01:RF-RFCalSys:OFSdB1-Mon', 0, 0.0], # [dB] + ['RA-RaSIA01:RF-RFCalSys:OFSdB2-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB3-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB4-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB5-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB6-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB7-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB8-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB9-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB10-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB11-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB12-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB13-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB14-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB15-Mon', 0, 0.0], + ['RA-RaSIA01:RF-RFCalSys:OFSdB16-Mon', 0, 0.0], ] _template_dict = { 'pvs': - _pvs_as_rf + - _pvs_li_llrf + _pvs_bo_llrf + _pvs_bo_rfssa + _pvs_bo_rfcal + + _pvs_as_rf + _pvs_li_llrf + + _pvs_bo_pow_sensor + _pvs_bo_llrf + _pvs_bo_rfssa + _pvs_bo_rfcal + _pvs_si_llrf + _pvs_si_rfssa + _pvs_si_rfcav + _pvs_si_rfcal } + \ No newline at end of file diff --git a/siriuspy/siriuspy/clientconfigdb/types/global_config.py b/siriuspy/siriuspy/clientconfigdb/types/global_config.py index 1ffd915c2..62f73194c 100644 --- a/siriuspy/siriuspy/clientconfigdb/types/global_config.py +++ b/siriuspy/siriuspy/clientconfigdb/types/global_config.py @@ -230,29 +230,6 @@ def get_dict(): ['AS-RaMO:TI-EVG:FOFBSDelayType-Sel', 0, 0.0], ['AS-RaMO:TI-EVG:FOFBSMode-Sel', 0, 0.0], - # AMCFPGAEVRs - ['IA-01RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-02RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-03RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-04RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-05RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-06RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-07RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-08RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-09RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-10RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-11RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-12RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-13RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-14RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-15RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-16RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-17RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-18RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-19RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-20RaBPM:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - ['IA-20RaBPMTL:TI-AMCFPGAEVR:FPGAClk-Cte', 124916500, 0.0], - # Triggers ['AS-Fam:TI-Scrn-TBBO:DelayRaw-SP', 0, 0], ['AS-Fam:TI-Scrn-TBBO:WidthRaw-SP', 0, 0.0], diff --git a/siriuspy/siriuspy/clientweb/implementation.py b/siriuspy/siriuspy/clientweb/implementation.py index d914ddef4..ffc9684fb 100644 --- a/siriuspy/siriuspy/clientweb/implementation.py +++ b/siriuspy/siriuspy/clientweb/implementation.py @@ -140,7 +140,7 @@ def crates_mapping(timeout=_TIMEOUT): txt = '' for fi in files: for time in read_url(url + fi, timeout=timeout).splitlines(): - txt += '{0:20s}'.format(fi[6:13]) + time + '\n' + txt += f'{time:<40s}' + f'{fi[6:13]:>10s}\n' txt += '\n\n' return txt diff --git a/siriuspy/siriuspy/devices/__init__.py b/siriuspy/siriuspy/devices/__init__.py index 7d1f0cf2c..3c2ebd9db 100644 --- a/siriuspy/siriuspy/devices/__init__.py +++ b/siriuspy/siriuspy/devices/__init__.py @@ -2,10 +2,11 @@ from .afc_acq_core import AFCPhysicalTrigger, AFCACQLogicalTrigger from .bbb import BunchbyBunch -from .bpm import BPM, FamBPMs +from .bpm import BPM +from .bpm_fam import FamBPMs from .bpm_eq import EqualizeBPMs -from .currinfo import CurrInfoTranspEff, CurrInfoLinear, \ - CurrInfoBO, CurrInfoSI, CurrInfoAS +from .currinfo import CurrInfoTranspEff, CurrInfoLinear, CurrInfoBO, \ + CurrInfoSI, CurrInfoAS from .dcct import DCCT from .device import Device, DeviceSet from .egun import EGBias, EGFilament, EGHVPS, EGTriggerPS, EGPulsePS, EGun @@ -15,8 +16,8 @@ from .fofb_acq import FOFBCtrlSysId, FOFBPSSysId, FamFOFBSysId, \ FOFBCtrlLamp, FOFBPSLamp, FamFOFBLamp from .ict import ICT, TranspEff -from .ids import APU, WIG, PAPU, EPU -from .idff import IDFF, WIGIDFF, PAPUIDFF, EPUIDFF, APUIDFF +from .ids import IDBase, APU, WIG, PAPU, EPU, DELTA, ID +from .idff import IDFF from .injctrl import InjCtrl from .injsys import PUMagsStandbyHandler, BOPSRampStandbyHandler, \ BORFRampStandbyHandler, InjSysStandbyHandler, LinacStandbyHandler, \ diff --git a/siriuspy/siriuspy/devices/bpm.py b/siriuspy/siriuspy/devices/bpm.py index 9236d864f..e460f1a6d 100644 --- a/siriuspy/siriuspy/devices/bpm.py +++ b/siriuspy/siriuspy/devices/bpm.py @@ -1,16 +1,14 @@ """BPM devices.""" -import sys import time as _time -# from threading import Event as _Flag import numpy as _np -from copy import deepcopy as _dcopy -from .device import Device as _Device, DeviceSet as _DeviceSet from ..diagbeam.bpm.csdev import Const as _csbpm from ..search import BPMSearch as _BPMSearch -from ..namesys import SiriusPVName as _PVName +from .device import Device as _Device + +# from threading import Event as _Flag class BPM(_Device): @@ -138,16 +136,6 @@ def __str__(self): stg += '\n' return stg - def __getitem__(self, propty): - """Return value of property.""" - propty = self.get_propname(propty) - return super().__getitem__(propty) - - def __setitem__(self, propty, value): - """Set value of property.""" - propty = self.get_propname(propty) - super().__setitem__(propty, value) - @property def is_ok(self): """.""" @@ -979,460 +967,3 @@ def get_propname(self, prop): def _get_pvname(self, propty): propty = self.get_propname(propty) return super()._get_pvname(propty) - - -class FamBPMs(_DeviceSet): - """Family of BPMs. - - Parameters - ---------- - devname (str, optional) - Device name. If not provided, defaults to DEVICES.SI. - Determine the list of BPM names. - bpmnames ((list, tuple), optional) - BPM names list. If provided, it takes priority over 'devname' - parameter. Defaults to None. - ispost_mortem (bool, optional) - Whether to control PM acquisition core. Defaults to False. - """ - - TIMEOUT = 10 - RFFEATT_MAX = 30 - PROPERTIES_ACQ = BPM.PROPERTIES_ACQ - PROPERTIES_DEFAULT = BPM.PROPERTIES_DEFAULT - ALL_MTURN_SIGNALS2ACQ = ('A', 'B', 'C', 'D', 'X', 'Y', 'Q', 'S') - - class DEVICES: - """.""" - - SI = 'SI-Fam:DI-BPM' - BO = 'BO-Fam:DI-BPM' - ALL = (BO, SI) - - def __init__( - self, devname=None, bpmnames=None, ispost_mortem=False, - props2init='all', mturn_signals2acq=('X', 'Y')): - """.""" - if devname is None: - devname = self.DEVICES.SI - if devname not in self.DEVICES.ALL: - raise ValueError('Wrong value for devname') - - devname = _PVName(devname) - bpm_names = bpmnames or _BPMSearch.get_names( - filters={'sec': devname.sec, 'dev': devname.dev}) - self._ispost_mortem = ispost_mortem - - self._mturn_signals2acq = list(mturn_signals2acq) - self.bpms = [BPM( - dev, auto_monitor_mon=False, ispost_mortem=ispost_mortem, - props2init=props2init) for dev in bpm_names] - - super().__init__(self.bpms[:], devname=devname) - self._bpm_names = bpm_names - self._csbpm = self.bpms[0].csdata - self._initial_timestamps = None - - self._mturn_flags = dict() - # NOTE: ACQCount-Mon need to be fixed on BPM's IOC - # for bpm in devs: - # pvo = bpm.pv_object('ACQCount-Mon') - # pvo.auto_monitor = True - # self._mturn_flags[pvo.pvname] = _Flag() - # pvo.add_callback(self._mturn_set_flag) - - @property - def bpm_names(self): - """Return BPM names.""" - return self._bpm_names - - @property - def csbpm(self): - """Return control system BPM constants class.""" - return self._csbpm - - @property - def mturn_signals2acq(self): - """Return which signals will be acquired by get_mturn_signals.""" - return _dcopy(self._mturn_signals2acq) - - @mturn_signals2acq.setter - def mturn_signals2acq(self, sigs): - sigs = [s.upper() for s in sigs] - diff = set(sigs) - set(self.ALL_MTURN_SIGNALS2ACQ) - if diff: - raise ValueError('The following signals do not exist: '+str(diff)) - self._mturn_signals2acq = sigs - - def set_attenuation(self, value=RFFEATT_MAX, timeout=TIMEOUT): - """.""" - for bpm in self: - bpm.rffe_att = value - - mstr = '' - okall = True - t0 = _time.time() - for bpm in self: - tout = timeout - (_time.time() - t0) - if not bpm._wait('RFFEAtt-RB', value, timeout=tout): - okall = False - mstr += ( - f'\n{bpm.devname:<20s}: ' + - f'rb {bpm.rffe_att:.0f} != sp {value:.0f}') - - print('RFFE attenuation set confirmed in all BPMs', end='') - print(', except:' + mstr if mstr else '.') - return okall - - def get_slow_orbit(self): - """Get slow orbit vectors. - - Returns: - orbx (numpy.ndarray, 160): Horizontal Orbit. - orby (numpy.ndarray, 160): Vertical Orbit. - - """ - orbx, orby = [], [] - for bpm in self.bpms: - orbx.append(bpm.posx) - orby.append(bpm.posy) - orbx = _np.array(orbx) - orby = _np.array(orby) - return orbx, orby - - def get_mturn_signals(self): - """Get Multiturn signals matrices. - - Returns: - list: Each component of the list is an numpy.ndarray with shape - (N, 160), containing the values for the signals acquired. - - """ - sigs = [[] for _ in self._mturn_signals2acq] - - mini = int(sys.maxsize) # a very large integer - for bpm in self.bpms: - for i, sn in enumerate(self._mturn_signals2acq): - sn = 'sum' if sn == 'S' else sn.lower() - name = 'mt_' + ('ampl' if sn in 'abcd' else 'pos') + sn - sigs[i].append(getattr(bpm, name)) - mini = min(mini, _np.min([s[-1].size for s in sigs])) - - for i, sig in enumerate(sigs): - for j, s in enumerate(sig): - sig[j] = s[:mini] - sigs[i] = _np.array(sig).T - return sigs - - def get_mturn_timestamps(self): - """Get Multiturn data timestamps. - - Returns: - tsmps (numpy.ndarray, (160, N)): The i-th row has the timestamp of - the i-th bpm for the N aquired signals. - - """ - tsmps = _np.zeros( - (len(self.bpms), len(self._mturn_signals2acq)), dtype=float) - for i, bpm in enumerate(self.bpms): - for j, s in enumerate(self._mturn_signals2acq): - s = 'SUM' if s == 'S' else s - pvo = bpm.pv_object(f'GEN_{s}ArrayData') - tv = pvo.get_timevars(timeout=self.TIMEOUT) - tsmps[i, j] = pvo.timestamp if tv is None else tv['timestamp'] - return tsmps - - def get_sampling_frequency(self, rf_freq: float, acq_rate='') -> float: - """Return the sampling frequency of the acquisition. - - Args: - rf_freq (float): RF frequency. - acq_rate (str, optional): acquisition rate. Defaults to ''. - If empty string, it gets the configured acq. rate on BPMs - - Returns: - float: acquisition frequency. - - """ - fs_bpms = { - dev.get_sampling_frequency(rf_freq, acq_rate) - for dev in self.bpms} - if len(fs_bpms) == 1: - return fs_bpms.pop() - else: - print('BPMs are not configured with the same ACQChannel.') - return None - - def get_switching_frequency(self, rf_freq: float) -> float: - """Return the switching frequency. - - Args: - rf_freq (float): RF frequency. - - Returns: - float: switching frequency. - - """ - fsw_bpms = { - dev.get_switching_frequency(rf_freq) for dev in self.bpms} - if len(fsw_bpms) == 1: - return fsw_bpms.pop() - else: - print('BPMs are not configured with the same SwMode.') - return None - - def mturn_config_acquisition( - self, nr_points_after: int, nr_points_before=0, - acq_rate='FAcq', repeat=True, external=True) -> int: - """Configure acquisition for BPMs. - - Args: - nr_points_after (int): number of points after trigger. - nr_points_before (int): number of points after trigger. - Defaults to 0. - acq_rate (str, optional): Acquisition rate ('TbT', 'TbTPha', - 'FOFB', 'FOFBPha', 'FAcq', 'ADC', 'ADCSwp'). - Defaults to 'FAcq'. - repeat (bool, optional): Whether or not acquisition should be - repetitive. Defaults to True. - external (bool, optional): Whether or not external trigger should - be used. Defaults to True. - - Returns: - int: code describing what happened: - =0: BPMs are ready. - <0: Index of the first BPM which did not stop last acq. plus 1. - >0: Index of the first BPM which is not ready for acq. plus 1. - - """ - if acq_rate.lower().startswith('facq'): - acq_rate = self._csbpm.AcqChan.FAcq - elif acq_rate.lower().startswith('fofbpha'): - acq_rate = self._csbpm.AcqChan.FOFBPha - elif acq_rate.lower().startswith('fofb'): - acq_rate = self._csbpm.AcqChan.FOFB - elif acq_rate.lower().startswith('tbtpha'): - acq_rate = self._csbpm.AcqChan.TbTPha - elif acq_rate.lower().startswith('tbt'): - acq_rate = self._csbpm.AcqChan.TbT - elif acq_rate.lower().startswith('adcswp'): - acq_rate = self._csbpm.AcqChan.ADCSwp - elif acq_rate.lower().startswith('adc'): - acq_rate = self._csbpm.AcqChan.ADC - else: - raise ValueError(acq_rate + ' is not a valid acquisition rate.') - - if repeat: - repeat = self._csbpm.AcqRepeat.Repetitive - else: - repeat = self._csbpm.AcqRepeat.Normal - - if external: - trig = self._csbpm.AcqTrigTyp.External - else: - trig = self._csbpm.AcqTrigTyp.Now - - ret = self.cmd_mturn_acq_abort() - if ret > 0: - return -ret - - for bpm in self.bpms: - bpm.acq_repeat = repeat - bpm.acq_channel = acq_rate - bpm.acq_trigger = trig - bpm.acq_nrsamples_pre = nr_points_before - bpm.acq_nrsamples_post = nr_points_after - - return self.cmd_mturn_acq_start() - - def cmd_mturn_acq_abort(self, wait=True, timeout=10) -> int: - """Abort BPMs acquistion. - - Args: - wait (bool, optional): whether or not to wait BPMs get ready. - Defaults to True. - timeout (int, optional): Time to wait. Defaults to 10. - - Returns: - int: code describing what happened: - =0: BPMs are ready. - >0: Index of the first BPM which did not update plus 1. - - """ - for bpm in self.bpms: - bpm.acq_ctrl = self._csbpm.AcqEvents.Abort - - if wait: - return self.wait_acquisition_finish(timeout=timeout) - return 0 - - def wait_acquisition_finish(self, timeout=10) -> int: - """Wait for all BPMs to be ready for acquisition. - - Args: - timeout (int, optional): Time to wait. Defaults to 10. - - Returns: - int: code describing what happened: - =0: BPMs are ready. - >0: Index of the first BPM which did not update plus 1. - - """ - for i, bpm in enumerate(self.bpms): - t0_ = _time.time() - if not bpm.wait_acq_finish(timeout): - return i + 1 - timeout -= _time.time() - t0_ - return 0 - - def cmd_mturn_acq_start(self, wait=True, timeout=10) -> int: - """Start BPMs acquisition. - - Args: - wait (bool, optional): whether or not to wait BPMs get ready. - Defaults to True. - timeout (int, optional): Time to wait. Defaults to 10. - - Returns: - int: code describing what happened: - =0: BPMs are ready. - >0: Index of the first BPM which did not update plus 1. - - """ - for bpm in self.bpms: - bpm.acq_ctrl = self._csbpm.AcqEvents.Start - if wait: - return self.wait_acquisition_start(timeout=timeout) - return 0 - - def wait_acquisition_start(self, timeout=10) -> bool: - """Wait for all BPMs to be ready for acquisition. - - Args: - timeout (int, optional): Time to wait. Defaults to 10. - - Returns: - int: code describing what happened: - =0: BPMs are ready. - >0: Index of the first BPM which did not update plus 1. - - """ - for i, bpm in enumerate(self.bpms): - t0_ = _time.time() - if not bpm.wait_acq_start(timeout): - return i + 1 - timeout -= _time.time() - t0_ - return 0 - - def set_switching_mode(self, mode='direct'): - """Set switching mode of BPMS. - - Args: - mode ((str, int), optional): Desired mode, must be in - {'direct', 'switching', 1, 3}. Defaults to 'direct'. - - Raises: - ValueError: When mode is not in {'direct', 'switching', 1, 3}. - - """ - if mode not in ('direct', 'switching', 1, 3): - raise ValueError('Value must be in ("direct", "switching", 1, 3).') - - for bpm in self.bpms: - bpm.switching_mode = mode - - def mturn_update_initial_timestamps(self): - """Call this method before acquisition to get orbit for comparison.""" - self._initial_timestamps = self.get_mturn_timestamps() - - def mturn_reset_flags(self): - """Reset Multiturn flags to wait for a new orbit update.""" - for flag in self._mturn_flags.values(): - flag.clear() - - def mturn_reset_flags_and_update_initial_timestamps(self): - """Set initial state to wait for orbit acquisition to start.""" - self.mturn_reset_flags() - self.mturn_update_initial_timestamps() - - def mturn_wait_update_flags(self, timeout=10): - """Wait for all acquisition flags to be updated. - - Args: - timeout (int, optional): Time to wait. Defaults to 10. - - Returns: - int: code describing what happened: - =0: BPMs are ready. - >0: Index of the first BPM which did not update plus 1. - - """ - for i, flag in enumerate(self._mturn_flags.values()): - t00 = _time.time() - if not flag.wait(timeout=timeout): - return i + 1 - timeout -= _time.time() - t00 - timeout = max(timeout, 0) - return 0 - - def mturn_wait_update_timestamps(self, timeout=10) -> int: - """Call this method after acquisition to check if data was updated. - - For this method to work it is necessary to call - mturn_update_initial_timestamps - before the acquisition starts, so that a reference for comparison is - created. - - Args: - timeout (int, optional): Waiting timeout. Defaults to 10. - - Returns: - int: code describing what happened: - -2: size of timestamps changed in relation to initial timestamp - -1: initial timestamps were not defined; - =0: data updated. - >0: index of the first BPM which did not update plus 1. - - """ - if self._initial_timestamps is None: - return -1 - - tsmp0 = self._initial_timestamps - while timeout > 0: - t00 = _time.time() - tsmp = self.get_mturn_timestamps() - if tsmp.size != tsmp0.size: - return -2 - errors = _np.any(_np.equal(tsmp, tsmp0), axis=1) - if not _np.any(errors): - return 0 - _time.sleep(0.1) - timeout -= _time.time() - t00 - - return int(_np.nonzero(errors)[0][0])+1 - - def mturn_wait_update(self, timeout=10) -> int: - """Combine all methods to wait update data. - - Args: - timeout (int, optional): Waiting timeout. Defaults to 10. - - Returns: - int: code describing what happened: - -2: size of timestamps changed in relation to initial timestamp - -1: initial timestamps were not defined; - =0: data updated. - >0: index of the first BPM which did not update plus 1. - - """ - t00 = _time.time() - ret = self.mturn_wait_update_flags(timeout) - if ret > 0: - return ret - timeout -= _time.time() - t00 - - return self.mturn_wait_update_timestamps(timeout) - - def _mturn_set_flag(self, pvname, **kwargs): - _ = kwargs - self._mturn_flags[pvname].set() diff --git a/siriuspy/siriuspy/devices/bpm_eq.py b/siriuspy/siriuspy/devices/bpm_eq.py index dd4634d68..f378a9ff0 100644 --- a/siriuspy/siriuspy/devices/bpm_eq.py +++ b/siriuspy/siriuspy/devices/bpm_eq.py @@ -7,7 +7,7 @@ from mathphys.functions import save as _save, load as _load, \ get_namedtuple as _namedtuple -from .bpm import FamBPMs as _FamBPMs +from .bpm_fam import FamBPMs as _FamBPMs from .timing import Trigger from .currinfo import CurrInfoSI as _CurrInfoSI @@ -32,13 +32,25 @@ def __init__(self, devname=None, bpmnames=None, logger=None): if logger is not None: self.logger = logger self._proc_method = self.ProcMethods.EABS - self._acq_strategy = self.AcqStrategies.AssumeOrder + self._acq_strategy = self.AcqStrategies.AcqInvRedGain self._acq_inverse_reduced_gain = self.round_gains(0.95) self._acq_nrpoints = 2000 self._acq_timeout = 30 super().__init__( devname=devname, bpmnames=bpmnames, ispost_mortem=False, - props2init=[], mturn_signals2acq='ABCD') + mturn_signals2acq='ABCD', props2init=[ + 'SwDirGainA-RB', 'SwDirGainB-RB', 'SwDirGainC-RB', + 'SwDirGainD-RB', 'SwInvGainA-RB', 'SwInvGainB-RB', + 'SwInvGainC-RB', 'SwInvGainD-RB', 'SwDirGainA-SP', + 'SwInvGainA-SP', 'SwDirGainB-SP', 'SwInvGainB-SP', + 'SwDirGainC-SP', 'SwInvGainC-SP', 'SwDirGainD-SP', + 'SwInvGainD-SP', 'ACQTriggerEvent-Sel', 'ACQStatus-Sts', + 'GEN_AArrayData', 'GEN_BArrayData', 'GEN_CArrayData', + 'GEN_DArrayData', 'ACQTriggerRep-Sel', 'ACQChannel-Sel', + 'ACQTrigger-Sel', 'ACQSamplesPre-SP', 'ACQSamplesPost-SP', + 'INFOHarmonicNumber-RB', 'INFOTbTRate-RB', 'SwDivClk-RB', + 'ACQChannel-Sts', 'INFOFOFBRate-RB', 'PosKx-RB', 'PosKy-RB', + 'PosXOffset-RB', 'PosYOffset-RB']) self.currinfo = _CurrInfoSI(props2init=['Current-Mon', ]) self.trigger = Trigger( 'SI-Fam:TI-BPM', props2init=['Src-Sel', 'Src-Sts']) @@ -158,10 +170,10 @@ def get_current_gains(self): """Return Current BPM gains as 3D numpy array. Returns: - numpy.ndarray (nrbpms, 4, 2): 3D numpy array with gains. - - The first index vary the BPMs, in the default high level + numpy.ndarray (4, nrbpms, 2): 3D numpy array with gains. + - The first index vary the antennas: 'A', 'B', 'C', 'D'; + - The second index vary the BPMs, in the default high level convention order; - - The second index vary the antennas: 'A', 'B', 'C', 'D'; - The last index refers to the type of the gain in the following order: 'Direct', 'Inverse'; @@ -171,14 +183,14 @@ def get_current_gains(self): gaind = [getattr(bpm, f'gain_direct_{a}') for a in 'abcd'] gaini = [getattr(bpm, f'gain_inverse_{a}') for a in 'abcd'] gains.append([gaind, gaini]) - gains = _np.array(gains).swapaxes(-1, -2) + gains = _np.array(gains).swapaxes(0, 1).swapaxes(0, -1) return gains def set_gains(self, gains): """Set gains matrix to BPMs. Args: - gains (float or numpy.ndarray, (nrbpms, 4, 2)): gain matrix. In + gains (float or numpy.ndarray, (4, nrbpms, 2)): gain matrix. In the same format as the one provided by get_current_gains method. If a float is provided, then this same gain will be applied to all BPMs. @@ -188,16 +200,16 @@ def set_gains(self, gains): """ nbpm = len(self.bpms) - shape = (nbpm, 4, 2) + shape = (4, nbpm, 2) if not isinstance(gains, _np.ndarray): gains = _np.full(shape, gains) if gains.shape != shape: raise ValueError(f'Wrong shape for gains. Must be {shape}') - for i, bpm in enumerate(self.bpms): - gns = gains[i] - for j, ant in enumerate('abcd'): - g = gns[j] + for j, ant in enumerate('abcd'): + gns = gains[j] + for i, bpm in enumerate(self.bpms): + g = gns[i] setattr(bpm, f'gain_direct_{ant}', g[0]) setattr(bpm, f'gain_inverse_{ant}', g[1]) @@ -216,7 +228,7 @@ def acquire_bpm_data(self): self.data['gains_init'] = self.get_current_gains() init_source = self.trigger.source - self.trigger.source = self.trigger.source_options.index('Clock3') + ini_dly = self.trigger.delay_raw try: self._do_acquire() except Exception as err: @@ -224,6 +236,7 @@ def acquire_bpm_data(self): self._log(f'ERR:{str(err)}') self.trigger.source = init_source + self.trigger.delay_raw = ini_dly self.set_gains(self.data['gains_init']) self._log('Acquisition Finished!') @@ -231,7 +244,7 @@ def _do_acquire(self): if self._acq_strategy == self.AcqStrategies.AssumeOrder: self.set_gains(self.MAX_MULTIPLIER) else: - gains = _np.full((len(self.bpms), 4, 2), self.MAX_MULTIPLIER) + gains = _np.full((4, len(self.bpms), 2), self.MAX_MULTIPLIER) gains[:, :, 1] *= self._acq_inverse_reduced_gain self.set_gains(gains) self.data['acq_strategy'] = self._acq_strategy @@ -239,23 +252,30 @@ def _do_acquire(self): # acquire antennas data in FOFB rate self._log('Preparing BPMs') - ret = self.cmd_mturn_acq_abort() + ret = self.cmd_abort_mturn_acquisition() if ret > 0: - self._log(f'ERR: BPM {ret-1} did not abort previous acquistion.') + self._log( + f'ERR: BPM {self.bpm_names[ret-1]} did not abort ' + 'previous acquistion.') return - self.mturn_reset_flags_and_update_initial_timestamps() - ret = self.mturn_config_acquisition( + self.reset_mturn_initial_state() + ret = self.config_mturn_acquisition( nr_points_after=self._acq_nrpoints, nr_points_before=0, acq_rate='FOFB', repeat=False, external=True) if ret > 0: - self._log(f'ERR: BPM {ret-1} did not start acquistion.') + self._log( + f'ERR: BPM {self.bpm_names[ret-1]} did not start acquistion.') return + self.trigger.delay_raw = 0 + self.trigger.source = self.trigger.source_options.index('Clock3') + self._log('Waiting BPMs to update') - ret = self.mturn_wait_update(timeout=self._acq_timeout) + ret = self.wait_update_mturn(timeout=self._acq_timeout) if ret > 0: - self._log(f'ERR: BPM {ret-1} did not update in time.') + self._log( + f'ERR: BPM {self.bpm_names[ret-1]} did not update in time.') return elif ret < 0: self._log(f'ERR: Problem with acquisition. Error code {ret}') @@ -289,7 +309,72 @@ def _do_acquire(self): _time.sleep(0.1) self.data['antennas'] = _np.array( - self.get_mturn_signals()).swapaxes(1, 0) + self.get_mturn_signals()).swapaxes(-1, -2) + + def acquire_data_for_checking(self): + """.""" + self._log('Starting Acquisition.') + + init_source = self.trigger.source + ini_dly = self.trigger.delay_raw + try: + self._do_acquire_for_check() + except Exception as err: + self._log('ERR:Problem with acquisition:') + self._log(f'ERR:{str(err)}') + + self.trigger.source = init_source + self.trigger.delay_raw = ini_dly + self._log('Acquisition Finished!') + + def _do_acquire_for_check(self): + # acquire antennas data in FOFB rate + self._log('Preparing BPMs') + ret = self.cmd_abort_mturn_acquisition() + if ret > 0: + self._log( + f'ERR: BPM {self.bpm_names[ret-1]} did not abort ' + 'previous acquistion.') + return + + fswtc = self.get_switching_frequency(1) + fsamp = self.get_sampling_frequency(1) + nrpts = int(fsamp / fswtc) + + self.reset_mturn_initial_state() + ret = self.config_mturn_acquisition( + nr_points_after=nrpts, nr_points_before=0, + acq_rate='FOFB', repeat=False, external=True) + if ret > 0: + self._log( + f'ERR: BPM {self.bpm_names[ret-1]} did not start acquistion.') + return + + self.trigger.delay_raw = 0 + self.trigger.source = self.trigger.source_options.index('Clock3') + + self._log('Waiting BPMs to update') + ret = self.wait_update_mturn(timeout=self._acq_timeout) + if ret > 0: + self._log( + f'ERR: BPM {self.bpm_names[ret-1]} did not update in time.') + return + elif ret < 0: + self._log(f'ERR: Problem with acquisition. Error code {ret}') + return + self._log('BPMs updated.') + + self._log('Acquiring data.') + if None in {fsamp, fswtc}: + self._log('ERR: Not all BPMs are configured equally.') + return + elif fsamp % (2*fswtc): + self._log('ERR: Sampling freq is not multiple of switching freq.') + return + + _time.sleep(0.1) + self.data['antennas_for_check'] = _np.array( + self.get_mturn_signals()).swapaxes(-1, -2) # --------- Methods for processing ------------ @@ -308,7 +393,7 @@ def calc_switching_levels( """Calculate average signal for each antenna in both switching states. Args: - antennas (numpy.ndarray, (nrbpms, 4, N)): Antennas data. + antennas (numpy.ndarray, (4, nrbpms, N)): Antennas data. fsamp (float, optional): Sampling frequency. Defaults to 4. fswtc (float, optional): Switching frequency. Defaults to 1. acq_strategy (int, optional): Whether we should assume states @@ -320,7 +405,7 @@ def calc_switching_levels( antennas gain during acquisition. Defaults to 0.95. Returns: - mean (numpy.ndarray, nrbpms, 4, 2): The levels of each switching + mean (numpy.ndarray, (4, nrbpms, 2)): The levels of each switching state for each antenna. """ @@ -337,20 +422,22 @@ def calc_switching_levels( elif ants.shape[-1] != trunc: ants = ants[:, :, :trunc] self._log(f'WARN:Truncating data at {trunc} points') - ants = ants.reshape(nbpm, nant, -1, lsemicyc*2) + ants = ants.reshape(nant, nbpm, -1, lsemicyc*2) ants = ants.mean(axis=2) self._log('Calculating switching levels.') self._log( f'AcqStrategy is {self.AcqStrategies._fields[acq_strategy]}.') if acq_strategy == self.AcqStrategies.AssumeOrder: - ants = ants.reshape(nbpm, nant, 2, lsemicyc) + ants = ants.reshape(nant, nbpm, 2, lsemicyc) mean = ants.mean(axis=-1) - idp = _np.tile(_np.arange(lsemicyc), (nbpm, 1)) - idn = idp + lsemicyc + # Inverse comes first!: + mean = mean[:, :, ::-1] + idn = _np.tile(_np.arange(lsemicyc), (nbpm, 1)) + idp = idn + lsemicyc else: # Try to find out the two states by looking at different levels in # the sum of the four antennas of each BPM. - dts = ants.sum(axis=1) + dts = ants.sum(axis=0) idcs = dts - dts.mean(axis=-1)[..., None] > 0 idp = idcs.nonzero() # direct idn = (~idcs).nonzero() # inverse @@ -360,11 +447,11 @@ def calc_switching_levels( self._log( 'ERR: Could not identify switching states appropriately.') return - mean = _np.zeros((nbpm, nant, 2)) - dtp = ants[idp[0], :, idp[1]].reshape(nbpm, lsemicyc, nant) - dtn = ants[idn[0], :, idn[1]].reshape(nbpm, lsemicyc, nant) - mean[:, :, 0] = dtp.mean(axis=1) - mean[:, :, 1] = dtn.mean(axis=1) + mean = _np.zeros((nant, nbpm, 2)) + dtp = ants[:, idp[0], idp[1]].reshape(nant, nbpm, lsemicyc) + dtn = ants[:, idn[0], idn[1]].reshape(nant, nbpm, lsemicyc) + mean[:, :, 0] = dtp.mean(axis=-1) + mean[:, :, 1] = dtn.mean(axis=-1) # Re-scale the inverse state data to match the direct state: scl = self.MAX_MULTIPLIER / acq_inverse_reduced_gain mean[:, :, 1] *= scl @@ -394,12 +481,12 @@ def calc_gains(self, mean): min_ant = min_ant[:, :, None] elif self._proc_method == self.ProcMethods.AABS: # equalize the 4 antennas for both semicycles - min_ant = mean.min(axis=-1).min(axis=-1) - min_ant = min_ant[:, None, None] + min_ant = mean.min(axis=0).min(axis=-1) + min_ant = min_ant[None, :, None] elif self._proc_method == self.ProcMethods.AAES: # equalize the 4 antennas for each semicycle - min_ant = mean.min(axis=1) - min_ant = min_ant[:, None, :] + min_ant = mean.min(axis=0) + min_ant = min_ant[None, :, :] min_ant *= maxm gains = self.round_gains(min_ant / mean) self.data['proc_method'] = self._proc_method @@ -408,21 +495,21 @@ def calc_gains(self, mean): def estimate_orbit_variation(self): """Estimate orbit variation between old and new gains.""" + self._log('Estimating Orbit Variation.') mean = self.data.get('antennas_mean') gains_init = self.data.get('gains_init') gains_new = self.data.get('gains_new') - posx_gain = self.data.get('posx_gain') - posy_gain = self.data.get('posy_gain') - posx_offset = self.data.get('posx_offset', _np.zeros(len(self.bpms))) - posy_offset = self.data.get('posy_offset', _np.zeros(len(self.bpms))) - + gnx = self.data.get('posx_gain')[:, None] + gny = self.data.get('posy_gain')[:, None] + ofx = self.data.get('posx_offset', _np.zeros(len(self.bpms)))[:, None] + ofy = self.data.get('posy_offset', _np.zeros(len(self.bpms)))[:, None] if gains_new is None: self._log('ERR:Missing info. Acquire and process data first.') - orbx_init, orby_init = self._estimate_orbit( - mean, gains_init, posx_gain, posy_gain, posx_offset, posy_offset) - orbx_new, orby_new = self._estimate_orbit( - mean, gains_new, posx_gain, posy_gain, posx_offset, posy_offset) + orbx_init, orby_init = self.calc_positions_from_amplitudes( + mean * gains_init, gnx, gny, ofx, ofy) + orbx_new, orby_new = self.calc_positions_from_amplitudes( + mean * gains_new, gnx, gny, ofx, ofy) # Get the average over both semicycles self.data['orbx_init'] = orbx_init.mean(axis=-1) self.data['orby_init'] = orby_init.mean(axis=-1) @@ -467,10 +554,10 @@ def plot_gains(self): 4, 2, figsize=(9, 8), sharex=True, sharey=True) ants = 'ABCD' for i in range(4): - ldo = axs[i, 0].plot(gini[:, i, 0])[0] - lio = axs[i, 1].plot(gini[:, i, 1], color=ldo.get_color())[0] - ldn = axs[i, 0].plot(gnew[:, i, 0])[0] - lin = axs[i, 1].plot(gnew[:, i, 1], color=ldn.get_color())[0] + ldo = axs[i, 0].plot(gini[i, :, 0])[0] + lio = axs[i, 1].plot(gini[i, :, 1], color=ldo.get_color())[0] + ldn = axs[i, 0].plot(gnew[i, :, 0])[0] + lin = axs[i, 1].plot(gnew[i, :, 1], color=ldn.get_color())[0] if not i: ldo.set_label('Old') ldn.set_label('New') @@ -508,8 +595,10 @@ def plot_semicycle_indices(self): def plot_antennas_mean(self): """.""" - posx_gain = self.data.get('posx_gain') - posy_gain = self.data.get('posx_gain') + gnx = self.data.get('posx_gain')[:, None] + gny = self.data.get('posx_gain')[:, None] + ofx = self.data.get('posx_offset')[:, None] + ofy = self.data.get('posx_offset')[:, None] gacq = self.data.get('gains_acq') gini = self.data.get('gains_init') gnew = self.data.get('gains_new') @@ -534,13 +623,14 @@ def plot_antennas_mean(self): val = antm * gain for i in range(4): ax = axs[i, j] - ld = ax.plot(val[:, i, 0], 'o-')[0] - li = ax.plot(val[:, i, 1], 'o-')[0] + ld = ax.plot(val[i, :, 0], 'o-')[0] + li = ax.plot(val[i, :, 1], 'o-')[0] if not i and not j: ld.set_label('Direct') li.set_label('Inverse') - posx, posy = self._estimate_orbit(antm, gain, posx_gain, posy_gain) - sum_ = val.sum(axis=1) + posx, posy = self.calc_positions_from_amplitudes( + antm * gain, gnx, gny, ofx, ofy) + sum_ = val.sum(axis=0) axs[4, j].plot(posx[:, 0], 'o-') axs[4, j].plot(posx[:, 1], 'o-') axs[5, j].plot(posy[:, 0], 'o-') @@ -559,27 +649,33 @@ def plot_antennas_mean(self): fig.tight_layout() return fig, axs - # ------- auxiliary methods ---------- + def plot_antennas_for_check(self): + """.""" + antd = self.data.get('antennas_for_check') + if antd is None: + self._log('ERR:Must acquire data for check first.') + return None, None + + fig, axs = _mplt.subplots( + 4, 1, figsize=(6, 8), sharex=True, sharey=True) + ants = 'ABCD' + mean = antd.mean(axis=-1)[:, :, None] + antd = antd / mean - 1 + nbpm = antd.shape[1] + for j in range(nbpm): + cor = _mplt.cm.jet(j/(nbpm-1)) + for i in range(4): + axs[i].plot(antd[i, j], 'o-', color=cor) + if not j: + axs[i].set_ylabel(ants[i] + ' [%]') + axs[i].grid(True, alpha=0.5, ls='--', lw=1) - def _estimate_orbit( - self, mean, gains, posx_gain, posy_gain, posx_offset, posy_offset): - ant = mean * gains - # Get pairs of antennas - ac = ant[:, ::2] - bd = ant[:, 1::2] - # Calculate difference over sum for each pair - dovs_ac = _np.diff(ac, axis=1).squeeze() / ac.sum(axis=1) - dovs_bd = _np.diff(bd, axis=1).squeeze() / bd.sum(axis=1) - # Get the positions: - posx = (dovs_ac - dovs_bd) - posy = (dovs_ac + dovs_bd) - # Apply Position gains and factor of two missing in previous step - posx *= posx_gain[:, None] / 2 / 1e3 - posy *= posy_gain[:, None] / 2 / 1e3 - # Subtract offsets: - posx -= posx_offset - posy -= posy_offset - return posx, posy + axs[0].set_title('Relative Variation') + axs[-1].set_xlabel('Samples') + fig.tight_layout() + return fig, axs + + # ------- auxiliary methods ---------- def _log(self, message, *args, level='INFO', **kwrgs): if self._logger is None: diff --git a/siriuspy/siriuspy/devices/bpm_fam.py b/siriuspy/siriuspy/devices/bpm_fam.py new file mode 100644 index 000000000..c0e475f9c --- /dev/null +++ b/siriuspy/siriuspy/devices/bpm_fam.py @@ -0,0 +1,640 @@ +"""FamBPM deviceSet.""" + +import logging as _log +import sys +import time as _time +from copy import deepcopy as _dcopy + +import numpy as _np + +from ..namesys import SiriusPVName as _PVName +from ..search import BPMSearch as _BPMSearch +from .bpm import BPM +from .device import DeviceSet as _DeviceSet + +# from threading import Event as _Flag + + +class FamBPMs(_DeviceSet): + """.""" + + TIMEOUT = 10 + RFFEATT_MAX = 30 + PROPERTIES_ACQ = BPM.PROPERTIES_ACQ + PROPERTIES_DEFAULT = BPM.PROPERTIES_DEFAULT + ALL_MTURN_SIGNALS2ACQ = ('A', 'B', 'C', 'D', 'X', 'Y', 'Q', 'S') + + class DEVICES: + """.""" + + SI = 'SI-Fam:DI-BPM' + BO = 'BO-Fam:DI-BPM' + ALL = (BO, SI) + + def __init__( + self, devname=None, bpmnames=None, ispost_mortem=False, + props2init='all', mturn_signals2acq=('X', 'Y')): + """Family of BPMs. + + Args: + devname (str, optional): Device name. If not provided, defaults to + DEVICES.SI. Determine the list of BPM names. + bpmnames ((list, tuple), optional): BPM names list. If provided, + it takes priority over 'devname' parameter. Defaults to None. + ispost_mortem (bool, optional): Whether to control PM acquisition + core. Defaults to False. + props2init (str|list|tuple, optional): list of properties to + initialize in object creation. If a string is passed, it can + be either "all" or "acq". Defaults to "all". + mturn_signals2acq (str|list|tuple, optional): Which signals to + acquire. Signals can be defined by: + X: x position; + Y: y position; + S: antennas sum signal; + Q: skew position; + A: amplitude of antenna A; + B: amplitude of antenna B; + C: amplitude of antenna C; + D: amplitude of antenna D. + Defaults to "XY". + + """ + if devname is None: + devname = self.DEVICES.SI + if devname not in self.DEVICES.ALL: + raise ValueError('Wrong value for devname') + + devname = _PVName(devname) + bpm_names = bpmnames or _BPMSearch.get_names( + filters={'sec': devname.sec, 'dev': devname.dev}) + self._ispost_mortem = ispost_mortem + + self._mturn_signals2acq = '' + self.bpms = [BPM( + dev, auto_monitor_mon=False, ispost_mortem=ispost_mortem, + props2init=props2init) for dev in bpm_names] + + # use property here to ensure auto_monitor + self.mturn_signals2acq = list(mturn_signals2acq) + + super().__init__(self.bpms[:], devname=devname) + self._bpm_names = bpm_names + self._csbpm = self.bpms[0].csdata + self._initial_timestamps = None + self._initial_signals = None + + self._mturn_flags = dict() + # NOTE: ACQCount-Mon need to be fixed on BPM's IOC + # for bpm in devs: + # pvo = bpm.pv_object('ACQCount-Mon') + # pvo.auto_monitor = True + # self._mturn_flags[pvo.pvname] = _Flag() + # pvo.add_callback(self._set_mturn_flag) + + @property + def bpm_names(self): + """Return BPM names.""" + return self._bpm_names + + @property + def csbpm(self): + """Return control system BPM constants class.""" + return self._csbpm + + @property + def mturn_signals2acq(self): + """Return which signals will be acquired by get_mturn_signals.""" + return _dcopy(self._mturn_signals2acq) + + @mturn_signals2acq.setter + def mturn_signals2acq(self, sigs): + sigs = [s.upper() for s in sigs] + diff = set(sigs) - set(self.ALL_MTURN_SIGNALS2ACQ) + if diff: + raise ValueError('The following signals do not exist: '+str(diff)) + self._configure_automonitor_acquisition_pvs(state=False) + self._mturn_signals2acq = sigs + self._configure_automonitor_acquisition_pvs(state=True) + + @staticmethod + def calc_positions_from_amplitudes( + amps, gainx=None, gainy=None, offx=0.0, offy=0.0 + ): + """Calculate transverse positions from antennas amplitudes. + + Args: + amps (list|numpy.ndarray, (4, ...)): A list or N-D array + containing the amplitudes of the four antennas in the order + A, B, C, D. + gainx (float|numpy.ndarray, optional): Horizontal gain in [um]. If + None, then the nominal value of 12/sqrt(2) of the storage ring + will be used. Defaults to None. An array can be used to + specify different values for each BPM. In this case the gain + array must match the shape of the antennas arrays or satisfy + the broadcast rules in relation to them. + gainy (float|numpy.ndarray, optional): Vertical gain in [um]. If + None, then the nominal value of 12/sqrt(2) of the storage ring + will be used. Defaults to None. An array can be used to + specify different values for each BPM. In this case the gain + array must match the shape of the antennas arrays or satisfy + the broadcast rules in relation to them. + offx (float|numpy.ndarray, optional): Horizontal offset in [um]. + Defaults to 0.0. An array can be used to specify different + values for each BPM. In this case the gain array must match + the shape of the antennas arrays or satisfy the broadcast + rules in relation to them. + offy (float|numpy.ndarray, optional): Vertical offset in [um]. + Defaults to 0.0. An array can be used to specify different + values for each BPM. In this case the gain array must match + the shape of the antennas arrays or satisfy the broadcast + rules in relation to them. + + Returns: + posx (numpy.ndarray, (...)): Horizontal position in [um]. Same + shape of antennas array. + posy (numpy.ndarray, (...)): Vertical position in [um]. Same + shape of antennas array. + + """ + a, b, c, d = amps + + radius = 12e3 # [um] + gainx = radius / _np.sqrt(2) if gainx is None else gainx + gainy = radius / _np.sqrt(2) if gainy is None else gainy + gainx = _np.atleast_1d(gainx) + gainy = _np.atleast_1d(gainy) + offx = _np.atleast_1d(offx) + offy = _np.atleast_1d(offy) + + # Calculate difference over sum for each pair + a_c = (a - c) / (a + c) + b_d = (b - d) / (b + d) + # Get the positions: + posx = (a_c - b_d) / 2 + posy = (a_c + b_d) / 2 + # Apply position gains: + posx *= gainx + posy *= gainy + # Subtract offsets: + posx -= offx + posy -= offy + return posx, posy + + def set_attenuation(self, value=RFFEATT_MAX, timeout=TIMEOUT): + """.""" + for bpm in self: + bpm.rffe_att = value + + mstr = '' + okall = True + t0 = _time.time() + for bpm in self: + tout = timeout - (_time.time() - t0) + if not bpm._wait('RFFEAtt-RB', value, timeout=tout): + okall = False + mstr += ( + f'\n{bpm.devname:<20s}: ' + + f'rb {bpm.rffe_att:.0f} != sp {value:.0f}') + + stg = ', except:' if mstr else '.' + _log.info('RFFE attenuation set confirmed in all BPMs%s', stg) + if mstr: + _log.info(mstr) + return okall + + def get_slow_orbit(self): + """Get slow orbit vectors. + + Returns: + orbx (numpy.ndarray, 160): Horizontal Orbit. + orby (numpy.ndarray, 160): Vertical Orbit. + + """ + orbx, orby = [], [] + for bpm in self.bpms: + orbx.append(bpm.posx) + orby.append(bpm.posy) + orbx = _np.array(orbx) + orby = _np.array(orby) + return orbx, orby + + def get_mturn_signals(self): + """Get Multiturn signals matrices. + + Returns: + list: Each component of the list is an numpy.ndarray with shape + (N, 160), containing the values for the signals acquired. + + """ + sigs = [[] for _ in self._mturn_signals2acq] + + mini = int(sys.maxsize) # a very large integer + for bpm in self.bpms: + for i, sn in enumerate(self._mturn_signals2acq): + sn = 'sum' if sn == 'S' else sn.lower() + name = 'mt_' + ('ampl' if sn in 'abcd' else 'pos') + sn + sigs[i].append(getattr(bpm, name)) + mini = min(mini, _np.min([s[-1].size for s in sigs])) + + for i, sig in enumerate(sigs): + for j, s in enumerate(sig): + sig[j] = s[:mini] + sigs[i] = _np.array(sig).T + return sigs + + def get_mturn_timestamps(self): + """Get Multiturn data timestamps. + + Returns: + tsmps (numpy.ndarray, (N, 160)): The i-th column has the timestamp + of the i-th bpm for the N aquired signals. + + """ + tsmps = _np.zeros( + (len(self._mturn_signals2acq), len(self.bpms)), dtype=float) + for i, s in enumerate(self._mturn_signals2acq): + for j, bpm in enumerate(self.bpms): + s = 'SUM' if s == 'S' else s + pvo = bpm.pv_object(f'GEN_{s}ArrayData') + tv = pvo.get_timevars(timeout=self.TIMEOUT) + tsmps[i, j] = pvo.timestamp if tv is None else tv['timestamp'] + return tsmps + + def get_sampling_frequency(self, rf_freq: float, acq_rate='') -> float: + """Return the sampling frequency of the acquisition. + + Args: + rf_freq (float): RF frequency. + acq_rate (str, optional): acquisition rate. Defaults to ''. + If empty string, it gets the configured acq. rate on BPMs + + Returns: + float: acquisition frequency. + + """ + fs_bpms = { + dev.get_sampling_frequency(rf_freq, acq_rate) + for dev in self.bpms} + if len(fs_bpms) == 1: + return fs_bpms.pop() + else: + _log.warning('BPMs are not configured with the same ACQChannel.') + return None + + def get_switching_frequency(self, rf_freq: float) -> float: + """Return the switching frequency. + + Args: + rf_freq (float): RF frequency. + + Returns: + float: switching frequency. + + """ + fsw_bpms = { + dev.get_switching_frequency(rf_freq) for dev in self.bpms} + if len(fsw_bpms) == 1: + return fsw_bpms.pop() + else: + _log.warning('BPMs are not configured with the same SwMode.') + return None + + def config_mturn_acquisition( + self, nr_points_after: int, nr_points_before=0, + acq_rate='FAcq', repeat=True, external=True) -> int: + """Configure acquisition for BPMs. + + Args: + nr_points_after (int): number of points after trigger. + nr_points_before (int): number of points after trigger. + Defaults to 0. + acq_rate (int|str, optional): Acquisition rate name ('TbT', + 'TbTPha', 'FOFB', 'FOFBPha', 'FAcq', 'ADC', 'ADCSwp') or value. + Defaults to 'FAcq'. + repeat (bool, optional): Whether or not acquisition should be + repetitive. Defaults to True. + external (bool, optional): Whether or not external trigger should + be used. Defaults to True. + + Returns: + int: code describing what happened: + =0: BPMs are ready. + <0: Index of the first BPM which did not stop last acq. plus 1. + >0: Index of the first BPM which is not ready for acq. plus 1. + + """ + dic = { + 'facq': self._csbpm.AcqChan.FAcq, + 'fofbpha': self._csbpm.AcqChan.FOFBPha, + 'fofb': self._csbpm.AcqChan.FOFB, + 'tbtpha': self._csbpm.AcqChan.TbTPha, + 'tbt': self._csbpm.AcqChan.TbT, + 'adcswp': self._csbpm.AcqChan.ADCSwp, + 'adc': self._csbpm.AcqChan.ADC, + } + if isinstance(acq_rate, str): + acq_rate = dic.get(acq_rate.lower()) + if acq_rate not in self._csbpm.AcqChan: + raise ValueError( + str(acq_rate) + ' is not a valid acquisition rate.') + + rep = self._csbpm.AcqRepeat.Normal + if repeat: + rep = self._csbpm.AcqRepeat.Repetitive + + trig = self._csbpm.AcqTrigTyp.Now + if external: + trig = self._csbpm.AcqTrigTyp.External + + ret = self.cmd_abort_mturn_acquisition() + if ret > 0: + return -ret + + for bpm in self.bpms: + bpm.acq_repeat = rep + bpm.acq_channel = acq_rate + bpm.acq_trigger = trig + bpm.acq_nrsamples_pre = nr_points_before + bpm.acq_nrsamples_post = nr_points_after + + return self.cmd_start_mturn_acquisition() + + def cmd_abort_mturn_acquisition(self, wait=True, timeout=10) -> int: + """Abort BPMs acquistion. + + Args: + wait (bool, optional): whether or not to wait BPMs get ready. + Defaults to True. + timeout (int, optional): Time to wait. Defaults to 10. + + Returns: + int: code describing what happened: + =0: BPMs are ready. + >0: Index of the first BPM which did not update plus 1. + + """ + for bpm in self.bpms: + bpm.acq_ctrl = self._csbpm.AcqEvents.Abort + + if wait: + return self.wait_acquisition_finish(timeout=timeout) + return 0 + + def wait_acquisition_finish(self, timeout=10) -> int: + """Wait for all BPMs to be ready for acquisition. + + Args: + timeout (int, optional): Time to wait. Defaults to 10. + + Returns: + int: code describing what happened: + =0: BPMs are ready. + >0: Index of the first BPM which did not update plus 1. + + """ + for i, bpm in enumerate(self.bpms): + t0_ = _time.time() + if not bpm.wait_acq_finish(timeout): + return i + 1 + timeout -= _time.time() - t0_ + return 0 + + def cmd_start_mturn_acquisition(self, wait=True, timeout=10) -> int: + """Start BPMs acquisition. + + Args: + wait (bool, optional): whether or not to wait BPMs get ready. + Defaults to True. + timeout (int, optional): Time to wait. Defaults to 10. + + Returns: + int: code describing what happened: + =0: BPMs are ready. + >0: Index of the first BPM which did not update plus 1. + + """ + for bpm in self.bpms: + bpm.acq_ctrl = self._csbpm.AcqEvents.Start + if wait: + return self.wait_acquisition_start(timeout=timeout) + return 0 + + def wait_acquisition_start(self, timeout=10) -> bool: + """Wait for all BPMs to be ready for acquisition. + + Args: + timeout (int, optional): Time to wait. Defaults to 10. + + Returns: + int: code describing what happened: + =0: BPMs are ready. + >0: Index of the first BPM which did not update plus 1. + + """ + for i, bpm in enumerate(self.bpms): + t0_ = _time.time() + if not bpm.wait_acq_start(timeout): + return i + 1 + timeout -= _time.time() - t0_ + return 0 + + def set_switching_mode(self, mode='direct'): + """Set switching mode of BPMS. + + Args: + mode ((str, int), optional): Desired mode, must be in + {'direct', 'switching', 1, 3}. Defaults to 'direct'. + + Raises: + ValueError: When mode is not in {'direct', 'switching', 1, 3}. + + """ + if mode not in ('direct', 'switching', 1, 3): + raise ValueError('Value must be in ("direct", "switching", 1, 3).') + + for bpm in self.bpms: + bpm.switching_mode = mode + + def update_mturn_initial_signals(self): + """Call this method before acquisition to get orbit for comparison.""" + sig = _np.array(self.get_mturn_signals()) + self._initial_signals = sig if sig.shape[1] != 0 else None + + def update_mturn_initial_timestamps(self): + """Call this method before acquisition to get orbit for comparison.""" + self._initial_timestamps = self.get_mturn_timestamps() + + def reset_mturn_flags(self): + """Reset Multiturn flags to wait for a new orbit update.""" + for flag in self._mturn_flags.values(): + flag.clear() + + def reset_mturn_initial_state(self): + """Set initial state to wait for orbit acquisition to start.""" + self.reset_mturn_flags() + self.update_mturn_initial_timestamps() + self.update_mturn_initial_signals() + + def wait_update_mturn_flags(self, timeout=10): + """Wait for all acquisition flags to be updated. + + Args: + timeout (int, optional): Time to wait. Defaults to 10. + + Returns: + int: code describing what happened: + =0: BPMs are ready. + >0: Index of the first BPM which did not update plus 1. + + """ + for i, flag in enumerate(self._mturn_flags.values()): + t00 = _time.time() + if not flag.wait(timeout=timeout): + return i + 1 + timeout -= _time.time() - t00 + timeout = max(timeout, 0) + return 0 + + def wait_update_mturn_timestamps(self, timeout=10) -> int: + """Call this method after acquisition to check for timestamps update. + + For this method to work it is necessary to call + update_mturn_initial_timestamps + before the acquisition starts, so that a reference for comparison is + created. + + Args: + timeout (int, optional): Waiting timeout. Defaults to 10. + + Returns: + int|float: code describing what happened: + -2: size of timestamps changed in relation to initial timestamp + -1: initial timestamps were not defined; + =0: data updated. + >0: timeout waiting BPMs. The returned value is a float of + form X.Y, where: + X: The integer part indicates the index of the first + BPM which did not update plus 1. + Y: The fractional part indicates which signal, from + 1 to N, that didn't update. + + """ + if self._initial_timestamps is None: + return -1 + + tsmp0 = self._initial_timestamps + while timeout > 0: + t00 = _time.time() + tsmp = self.get_mturn_timestamps() + if tsmp.shape != tsmp0.shape: + return -2 + errors = _np.equal(tsmp, tsmp0) + if not _np.any(errors): + return 0 + _time.sleep(0.1) + timeout -= _time.time() - t00 + + bpm_idx = int(_np.nonzero(_np.any(errors, axis=0))[0][0]) + code = bpm_idx + 1 + code += (int(_np.nonzero(errors[:, bpm_idx])[0][0]) + 1) / 10 + return code + + def wait_update_mturn_signals(self, timeout=10) -> int: + """Call this method after acquisition to check for data update. + + For this method to work it is necessary to call + update_mturn_initial_signals + before the acquisition starts, so that a reference for comparison is + created. + + Args: + timeout (int, optional): Waiting timeout. Defaults to 10. + + Returns: + int|float: code describing what happened: + -2: size of signals is 0; + -1: initial signals were not defined; + =0: signals updated. + >0: timeout waiting BPMs. The returned value is a float of + form X.Y, where: + X: The integer part indicates the index of the first + BPM which did not update plus 1. + Y: The fractional part indicates which signal, from + 1 to N, that didn't update. + + """ + if self._initial_signals is None: + return -1 + + sig0 = self._initial_signals + while timeout > 0: + t00 = _time.time() + sig = _np.array(self.get_mturn_signals()) + siz = min(sig.shape[1], sig0.shape[1]) + if siz == 0: + return -2 + errors = _np.all(_np.equal(sig[:, :siz], sig0[:, :siz]), axis=1) + if not _np.any(errors): + return 0 + _log.debug('Signals did not update yet. Trying again.') + _time.sleep(0.1) + timeout -= _time.time() - t00 + + bpm_idx = int(_np.nonzero(_np.any(errors, axis=0))[0][0]) + code = bpm_idx + 1 + code += (int(_np.nonzero(errors[:, bpm_idx])[0][0]) + 1) / 10 + return code + + def wait_update_mturn(self, timeout=10) -> int: + """Combine all methods to wait update data. + + Args: + timeout (int, optional): Waiting timeout. Defaults to 10. + + Returns: + int|float: code describing what happened: + -2: size of timestamps changed in relation to initial timestamp + -1: initial timestamps were not defined; + =0: data updated. + >0: timeout waiting BPMs. The returned value is a float of + form X.Y, where: + X: The integer part indicates the index of the first + BPM which did not update plus 1. + Y: The fractional part indicates which signal, from + 1 to N, that didn't update. + + """ + t00 = _time.time() + ret = self.wait_update_mturn_flags(timeout) + if ret > 0: + return ret + t01 = _time.time() + dtime = t01 - t00 + timeout -= dtime + _log.debug("Flags updated (ETA: %.3fs).", dtime) + + ret = self.wait_update_mturn_timestamps(timeout) + if ret != 0: + return ret + t02 = _time.time() + dtime = t02 - t01 + timeout -= dtime + _log.debug("Timestamps updated (ETA: %.3fs).", dtime) + + ret = self.wait_update_mturn_signals(timeout) + if ret != 0: + return ret + dtime = _time.time() - t02 + _log.debug("Data updated (ETA: %.3fs).", dtime) + return ret + + # ---------------------- Auxiliary methods ------------------------------ + def _configure_automonitor_acquisition_pvs(self, state): + for bpm in self.bpms: + for sig in self._mturn_signals2acq: + sig = 'SUM' if sig.upper() == 'S' else sig.upper() + bpm.pv_object(f'GEN_{sig}ArrayData').auto_monitor = state + + def _set_mturn_flag(self, pvname, **kwargs): + _ = kwargs + self._mturn_flags[pvname].set() diff --git a/siriuspy/siriuspy/devices/device.py b/siriuspy/siriuspy/devices/device.py index 41031f7e4..ce2647226 100644 --- a/siriuspy/siriuspy/devices/device.py +++ b/siriuspy/siriuspy/devices/device.py @@ -4,7 +4,6 @@ import operator as _opr import math as _math from functools import partial as _partial -from copy import deepcopy as _dcopy from epics.ca import ChannelAccessGetFailure as _ChannelAccessGetFailure, \ CASeverityException as _CASeverityException @@ -78,19 +77,20 @@ def devname(self): @property def properties_in_use(self): """Return properties that were already added to the PV list.""" - return sorted(self._pvs.keys()) + return tuple(sorted(self._pvs.keys())) @property def properties_added(self): """Return properties that were added to the PV list that are not in PROPERTIES_DEFAULT.""" - return sorted( - set(self.properties_in_use) - set(self.PROPERTIES_DEFAULT)) + return tuple(sorted( + set(self.properties_in_use) - set(self.PROPERTIES_DEFAULT))) @property def properties_all(self): """Return all properties of the device, connected or not.""" - return sorted(set(self.PROPERTIES_DEFAULT + self.properties_in_use)) + return tuple(sorted( + set(self.PROPERTIES_DEFAULT + self.properties_in_use))) @property def simulators(self): @@ -200,7 +200,10 @@ def __getitem__(self, propty): def __setitem__(self, propty, value): """Set value of property.""" pvobj = self.pv_object(propty) - pvobj.value = value + try: + pvobj.value = value + except (_ChannelAccessGetFailure, _CASeverityException): + print('Could not set value of {}'.format(pvobj.pvname)) # --- private methods --- def _create_pv(self, propty): @@ -225,23 +228,37 @@ def comp_(val): if isinstance(comp, str): comp = getattr(_opr, comp) - if comp_(value): - return True - - timeout = _DEF_TIMEOUT if timeout is None else timeout - ntrials = int(timeout/_TINY_INTERVAL) - for _ in range(ntrials): + if not isinstance(timeout, str) and timeout != 'never': + timeout = _DEF_TIMEOUT if timeout is None else timeout + timeout = 0 if timeout <= 0 else timeout + t0_ = _time.time() + while not comp_(value): + if isinstance(timeout, str) and timeout == 'never': + pass + else: + if _time.time() - t0_ > timeout: + return False _time.sleep(_TINY_INTERVAL) - if comp_(value): - return True - return False + return True def _wait_float( self, propty, value, rel_tol=0.0, abs_tol=0.1, timeout=None): """Wait until float value gets close enough of desired value.""" - func = _partial(_math.isclose, abs_tol=abs_tol, rel_tol=rel_tol) + isc = _np.isclose if isinstance(value, _np.ndarray) else _math.isclose + func = _partial(isc, abs_tol=abs_tol, rel_tol=rel_tol) return self._wait(propty, value, comp=func, timeout=timeout) + def _wait_set(self, props_values, timeout=None, comp='eq'): + timeout = _DEF_TIMEOUT if timeout is None else timeout + t0_ = _time.time() + for propty, value in props_values.items(): + timeout_left = max(0, timeout - (_time.time() - t0_)) + if timeout_left == 0: + return False + if not self._wait(propty, value, timeout=timeout_left, comp=comp): + return False + return True + def _get_pvname(self, propty): dev = self._devname pref = _VACA_PREFIX + ('-' if _VACA_PREFIX else '') diff --git a/siriuspy/siriuspy/devices/dvf.py b/siriuspy/siriuspy/devices/dvf.py index 7f9c594d2..1e9917360 100644 --- a/siriuspy/siriuspy/devices/dvf.py +++ b/siriuspy/siriuspy/devices/dvf.py @@ -420,7 +420,7 @@ def cmd_cam_roi_set(self, offsetx, offsety, width, height, timeout=None): """Set cam image ROI and reset aquisition.""" c_width, c_height = self.cam_width, self.cam_height n_width, n_height = int(width), int(height) - + if not self.cmd_acquire_off(timeout=timeout): return False if n_width < c_width: diff --git a/siriuspy/siriuspy/devices/energy.py b/siriuspy/siriuspy/devices/energy.py index d50afe7d5..ff155c323 100644 --- a/siriuspy/siriuspy/devices/energy.py +++ b/siriuspy/siriuspy/devices/energy.py @@ -35,22 +35,22 @@ def __init__(self, devname): @property def energy(self): """Return Ref-Mon energy.""" - return self.value_get('EnergyRef-Mon') + return self.get_value('EnergyRef-Mon') @energy.setter def energy(self, value): """Set energy.""" - self.value_set('Energy-SP', value) + self.set_value('Energy-SP', value) @property def energy_sp(self): """Return -SP energy.""" - return self.value_get('Energy-SP') + return self.get_value('Energy-SP') @property def energy_mon(self): """Return -Mon energy.""" - return self.value_get('Energy-Mon') + return self.get_value('Energy-Mon') @staticmethod def _get_dipole_devnames(devname): diff --git a/siriuspy/siriuspy/devices/idff.py b/siriuspy/siriuspy/devices/idff.py index 467c28416..f18e2d3b4 100644 --- a/siriuspy/siriuspy/devices/idff.py +++ b/siriuspy/siriuspy/devices/idff.py @@ -1,4 +1,4 @@ -"""Insertion Devices Feedforward Devices.""" +"""Insertion Device Feedforward Devices.""" from ..namesys import SiriusPVName as _SiriusPVName from ..search import IDSearch as _IDSearch @@ -6,15 +6,14 @@ from .device import Device as _Device, DeviceSet as _DeviceSet from .pwrsupply import PowerSupplyFBP as _PowerSupplyFBP -from .ids import WIG as _WIG, APU as _APU, PAPU as _PAPU, EPU as _EPU +from .ids import ID as _ID class IDFF(_DeviceSet): """Insertion Device Feedforward Device.""" - class DEVICES(_WIG.DEVICES, _PAPU.DEVICES, _EPU.DEVICES): + class DEVICES(_ID.DEVICES): """.""" - ALL = _WIG.DEVICES.ALL + _PAPU.DEVICES.ALL + _EPU.DEVICES.ALL def __init__(self, devname): """.""" @@ -24,19 +23,18 @@ def __init__(self, devname): if devname not in IDFF.DEVICES.ALL: raise NotImplementedError(devname) - self._devname = _SiriusPVName(devname) # needed for _create_devices + self._devname = devname # needed for _create_devices self._idffconfig = _IDFFConfig() self._pparametername = \ _IDSearch.conv_idname_2_pparameter_propty(devname) - self._kparametername = \ _IDSearch.conv_idname_2_kparameter_propty(devname) - self._devpp, self._devkp, self._devsch, self._devscv, self._devsqs = \ + self._devid, self._devsch, self._devscv, self._devsqs = \ self._create_devices(devname) - devices = [self._devpp, self._devkp] + devices = [self._devid, ] devices += self._devsch devices += self._devscv devices += self._devsqs @@ -57,6 +55,11 @@ def qsnames(self): """Return QS corrector power supply names.""" return _IDSearch.conv_idname_2_idff_qsnames(self.devname) + @property + def iddev(self): + """Return ID device.""" + return self._devid + @property def chdevs(self): """Return CH corrector power supply devices.""" @@ -87,15 +90,20 @@ def polarizations(self): """Return list of possible light polarizations for the ID.""" return _IDSearch.conv_idname_2_polarizations(self.devname) + @property + def polarization_mon(self): + """Return current ID polarization as a string (or None).""" + return self.iddev.polarization_mon_str + @property def pparameter_mon(self): """Return pparameter value.""" - return self._devpp[self._pparametername] + return self._devid[self._pparametername] @property def kparameter_mon(self): """Return kparameter value.""" - return self._devkp[self._kparametername] + return self._devid[self._kparametername] @property def idffconfig(self): @@ -131,10 +139,12 @@ def calculate_setpoints( if polarization not in self.idffconfig.polarizations: raise ValueError('Polarization is not compatible with ID.') + if pparameter_value is None: + pparameter_value = self.pparameter_mon if kparameter_value is None: kparameter_value = self.kparameter_mon setpoints = self.idffconfig.calculate_setpoints( - polarization, kparameter_value) + polarization, pparameter_value, kparameter_value) return setpoints, polarization, pparameter_value, kparameter_value def implement_setpoints( @@ -145,6 +155,8 @@ def implement_setpoints( self.calculate_setpoints( pparameter_value=None, kparameter_value=None) + else: + polarization, pparameter_value, kparameter_value = [None, ] * 3 if corrdevs is None: corrdevs = self._devsch + self._devscv + self._devsqs for pvname, value in setpoints.items(): @@ -229,74 +241,21 @@ def get_polarization_state( kparameter_value = self.kparameter_mon if None in (pparameter_value, kparameter_value): return None, pparameter_value, kparameter_value - polarization = self.idffconfig.get_polarization_state( - pparameter=pparameter_value, kparameter=kparameter_value) + polarization = self.polarization_mon + if polarization is None: + polarization = self.idffconfig.get_polarization_state( + pparameter=pparameter_value, kparameter=kparameter_value) return polarization, pparameter_value, kparameter_value def _create_devices(self, devname): - param_auto_mon = False - devpp = _Device( - devname=devname, - props2init=(self._pparametername, ), - auto_monitor_mon=param_auto_mon) - devkp = _Device( - devname=devname, - props2init=(self._kparametername, ), - auto_monitor_mon=param_auto_mon) + pol_mon = _ID.get_idclass(devname).PARAM_PVS.POL_MON + params = ( + self._pparametername, self._kparametername, pol_mon) + props2init = tuple(param for param in params if param is not None) + devid = _ID( + devname=devname, props2init=props2init, + auto_monitor_mon=False) devsch = [_PowerSupplyFBP(devname=dev) for dev in self.chnames] devscv = [_PowerSupplyFBP(devname=dev) for dev in self.cvnames] devsqs = [_PowerSupplyFBP(devname=dev) for dev in self.qsnames] - return devpp, devkp, devsch, devscv, devsqs - - -class WIGIDFF(IDFF): - """Wiggler Feedforward.""" - - class DEVICES(_WIG.DEVICES): - """.""" - - @property - def gap_mon(self): - """.""" - return self.kparameter_mon - - -class PAPUIDFF(IDFF): - """PAPU Feedforward.""" - - class DEVICES(_PAPU.DEVICES): - """.""" - - @property - def phase_mon(self): - """.""" - return self.pparameter_mon - - -class EPUIDFF(IDFF): - """EPU Feedforward.""" - - class DEVICES(_EPU.DEVICES): - """.""" - - @property - def phase_mon(self): - """.""" - return self.pparameter_mon - - @property - def gap_mon(self): - """.""" - return self.kparameter_mon - - -class APUIDFF(_DeviceSet): - """APU Feedforward.""" - - class DEVICES(_APU.DEVICES): - """.""" - - @property - def phase_mon(self): - """.""" - return self.kparameter_mon + return devid, devsch, devscv, devsqs diff --git a/siriuspy/siriuspy/devices/ids.py b/siriuspy/siriuspy/devices/ids.py index c53d79173..d3a70335c 100644 --- a/siriuspy/siriuspy/devices/ids.py +++ b/siriuspy/siriuspy/devices/ids.py @@ -1,134 +1,445 @@ -"""Define Insertion Devices.""" +"""Insertion Devices.""" import time as _time +import inspect as _inspect +import numpy as _np -from ..namesys import SiriusPVName as _SiriusPVName +from ..search import IDSearch as _IDSearch from .device import Device as _Device -class APU(_Device): - """APU Insertion Device.""" - - class DEVICES: - """Device names.""" - - APU22_06SB = 'SI-06SB:ID-APU22' - APU22_07SP = 'SI-07SP:ID-APU22' - APU22_08SB = 'SI-08SB:ID-APU22' - APU22_09SA = 'SI-09SA:ID-APU22' - APU58_11SP = 'SI-11SP:ID-APU58' - ALL = ( - APU22_06SB, APU22_07SP, APU22_08SB, APU22_09SA, APU58_11SP, ) - - TOLERANCE_PHASE = 0.01 # [mm] +class _PARAM_PVS: + """.""" + + # --- GENERAL --- + PERIOD_LEN_CTE = None + START_PARKING_CMD = None + IS_MOVING = 'Moving-Mon' + BLCTRL_ENBL_SEL = 'BeamLineCtrlEnbl-Sel' + BLCTRL_ENBL_STS = 'BeamLineCtrlEnbl-Sts' + MOVE_ABORT = None + + # --- PPARAM --- + PPARAM_SP = None + PPARAM_RB = None + PPARAM_MON = None + PPARAM_PARKED_CTE = None + PPARAM_MAXACC_SP = None + PPARAM_MAXACC_RB = None + PPARAM_MAXVELO_SP = None + PPARAM_MAXVELO_RB = None + PPARAM_VELO_SP = None + PPARAM_VELO_RB = None + PPARAM_VELO_MON = None + PPARAM_ACC_SP = None + PPARAM_ACC_RB = None + PPARAM_TOL_SP = None + PPARAM_TOL_RB = None + PPARAM_CHANGE_CMD = None + + # --- KPARAM --- + KPARAM_SP = None + KPARAM_RB = None + KPARAM_MON = None + KPARAM_PARKED_CTE = None + KPARAM_MAXACC_SP = None + KPARAM_MAXACC_RB = None + KPARAM_MAXVELO_SP = None + KPARAM_MAXVELO_RB = None + KPARAM_VELO_SP = None + KPARAM_VELO_RB = None + KPARAM_VELO_MON = None + KPARAM_ACC_SP = None + KPARAM_ACC_RB = None + KPARAM_TOL_SP = None + KPARAM_TOL_RB = None + KPARAM_CHANGE_CMD = None + + # --- POL --- + POL_SEL = None + POL_STS = None + POL_MON = None + POL_CHANGE_CMD = None + + def __str__(self): + """Print parameters.""" + str_ = '' + strf = '{}: {}' + for key, value in _inspect.getmembers(self): + if not key.startswith('_') and value is not None: + lstr = strf.format(key, value) + str_ += lstr if str_ == '' else '\n' + lstr + return str_ + + +class IDBase(_Device): + """Base Insertion Device.""" _SHORT_SHUT_EYE = 0.1 # [s] - _CMD_MOVE_STOP, _CMD_MOVE_START = 1, 3 - _MOVECHECK_SLEEP = 0.1 # [s] - _default_timeout = 8 # [s] - - PROPERTIES_DEFAULT = ( - 'BeamLineCtrlEnbl-Sel', 'BeamLineCtrlEnbl-Sts', - 'DevCtrl-Cmd', 'Moving-Mon', - 'MaxPhaseSpeed-SP', 'MaxPhaseSpeed-RB', - 'PhaseSpeed-SP', 'PhaseSpeed-Mon', - 'Phase-SP', 'Phase-Mon', - 'Kx-SP', 'Kx-Mon', - ) + _DEF_TIMEOUT = 8 # [s] - _DEF_TIMEOUT = 10 # [s] - _CMD_MOVE = 3 - _MOVECHECK_SLEEP = 0.1 # [s] + PARAM_PVS = _PARAM_PVS() + + PROPERTIES_DEFAULT = \ + tuple(set(value for key, value in _inspect.getmembers(PARAM_PVS) \ + if not key.startswith('_') and value is not None)) def __init__(self, devname, props2init='all', auto_monitor_mon=True): """.""" - # check if device exists - if devname not in APU.DEVICES.ALL: - raise NotImplementedError(devname) - # call base class constructor super().__init__( devname, props2init=props2init, auto_monitor_mon=auto_monitor_mon) + self._pols_sel_str = \ + _IDSearch.conv_idname_2_polarizations(self.devname) + self._pols_sts_str = \ + _IDSearch.conv_idname_2_polarizations_sts(self.devname) + + # --- general --- + + @property + def parameters(self): + """Return ID parameters.""" + return _IDSearch.conv_idname_2_parameters(self.devname) @property def period_length(self): """Return ID period length [mm].""" - _, length = self.devname.split('APU') - return float(length) + if self.PARAM_PVS.PERIOD_LEN_CTE in self.properties_all: + return self[self.PARAM_PVS.PERIOD_LEN_CTE] + else: + return self.parameters.PERIOD_LENGTH - # --- phase speeds ---- + # --- polarization --- @property - def phase_speed(self): - """Return phase speed [mm/s].""" - return self['PhaseSpeed-RB'] + def polarization(self): + """Return ID polarization.""" + if self.PARAM_PVS.POL_STS in self.properties_all: + return self[self.PARAM_PVS.POL_STS] + else: + if _IDSearch.POL_UNDEF_STR in self._pols_sts_str: + return self._pols_sts_str.index(_IDSearch.POL_UNDEF_STR) + else: + return None @property - def phase_speed_mon(self): - """Return phase speed monitor [mm/s].""" - return self['PhaseSpeed-Mon'] + def polarization_str(self): + """Return ID polarization string.""" + pol_idx = self.polarization + if pol_idx is None: + return None + else: + return self._pols_sts_str[pol_idx] + + @polarization.setter + def polarization(self, value): + """Set ID polarization.""" + if self.PARAM_PVS.POL_SEL in self.properties_all: + if isinstance(value, str): + value = self._pols_sel_str.index(value) + self[self.PARAM_PVS.POL_SEL] = value @property - def phase_speed_max(self): - """Return max phase speed readback [mm/s].""" - return self['MaxPhaseSpeed-RB'] + def polarization_mon(self): + """Return ID polarization monitor.""" + if self.PARAM_PVS.POL_MON in self.properties_all: + return self[self.PARAM_PVS.POL_MON] + else: + return None @property - def phase_speed_max_lims(self): - """Return max phase speed limits.""" - ctrl = self.pv_ctrlvars('MaxPhaseSpeed-SP') + def polarization_mon_str(self): + """Return ID polarization string.""" + pol_idx = self.polarization_mon + if pol_idx is None: + return None + else: + return self._pols_sts_str[pol_idx] + + # --- pparameter --- + + @property + def pparameter_tol(self): + """PParameter position tolerance [mm].""" + if self.PARAM_PVS.PPARAM_TOL_RB is None: + return self.parameters.PPARAM_TOL + else: + return self[self.PARAM_PVS.PPARAM_TOL_RB] + + @property + def pparameter_parked(self): + """Return ID parked pparameter value [mm].""" + if self.PARAM_PVS.PPARAM_PARKED_CTE in self.properties_all: + return self[self.PARAM_PVS.PPARAM_PARKED_CTE] + else: + return self.parameters.PPARAM_PARKED + + @property + def pparameter_speed_max(self): + """Return max pparameter speed readback [mm/s].""" + if self.PARAM_PVS.PPARAM_MAXVELO_RB is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_MAXVELO_RB] + + @property + def pparameter_speed_max_lims(self): + """Return max pparameter speed limits.""" + if self.PARAM_PVS.PPARAM_MAXVELO_RB is None: + return None + else: + ctrl = self.pv_ctrlvars(self.PARAM_PVS.PPARAM_MAXVELO_SP) + lims = [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] + return lims + + @property + def pparameter_speed(self): + """Return pparameter speed readback [mm/s].""" + if self.PARAM_PVS.PPARAM_VELO_RB is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_VELO_SP] + + @property + def pparameter_speed_mon(self): + """Return pparameter speed monitor [mm/s].""" + if self.PARAM_PVS.PPARAM_VELO_MON is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_VELO_MON] + + @property + def pparameter_accel_max(self): + """Return maximum pparameter acceleration [mm/s²].""" + if self.PARAM_PVS.PPARAM_MAXACC_RB is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_MAXACC_RB] + + @property + def pparameter_accel_max_lims(self): + """Return max pparameter accel limits.""" + if self.PARAM_PVS.PPARAM_MAXACC_RB is None: + return None + else: + ctrl = self.pv_ctrlvars(self.PARAM_PVS.PPARAM_MAXACC_SP) + lims = [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] + return lims + + @property + def pparameter_accel(self): + """Return pparameter acceleration [mm/s²].""" + if self.PARAM_PVS.PPARAM_ACC_RB is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_ACC_RB] + + @property + def pparameter_lims(self): + """Return ID pparameter lower control limit [mm].""" + if self.PARAM_PVS.PPARAM_VELO_SP is None: + return None + else: + ctrl = self.pv_ctrlvars(self.PARAM_PVS.PPARAM_SP) + return [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] + + @property + def pparameter(self): + """Return ID pparameter readback [mm].""" + if self.PARAM_PVS.PPARAM_RB is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_RB] + + @property + def pparameter_mon(self): + """Return ID pparameter monitor [mm].""" + if self.PARAM_PVS.PPARAM_MON is None: + return None + else: + return self[self.PARAM_PVS.PPARAM_MON] + + @property + def pparameter_move_eta(self): + """Return estimated moving time to reach pparameter RB position.""" + # NOTE: the IOC may provide this as PV in the future + pparam_eta, _ = self.calc_move_eta(self.pparameter, None) + return pparam_eta + + def set_pparameter(self, pparam, timeout=None): + """Set ID target pparameter for movement [mm].""" + if self.PARAM_PVS.PPARAM_SP is None: + return True + else: + return self._write_sp(self.PARAM_PVS.PPARAM_SP, pparam, timeout) + + def set_pparameter_speed(self, pparam_speed, timeout=None): + """Command to set ID cruise pparameterspeed for movement [mm/s].""" + if self.PARAM_PVS.PPARAM_VELO_SP is None: + return True + else: + return self._write_sp( + self.PARAM_PVS.PPARAM_VELO_SP, pparam_speed, timeout) + + def set_pparameter_speed_max(self, pparam_speed_max, timeout=None): + """Command to set ID max cruise pparam speed for movement [mm/s].""" + if self.PARAM_PVS.PPARAM_MAXVELO_SP is None: + return True + else: + return self._write_sp( + self.PARAM_PVS.PPARAM_MAXVELO_SP, pparam_speed_max, timeout) + + def set_pparameter_accel(self, pparam_accel, timeout=None): + """Command to set ID pparam accel for movement [mm/s²].""" + if self.PARAM_PVS.PPARAM_ACC_SP is None: + return True + else: + return self._write_sp( + self.PARAM_PVS.PPARAM_ACC_SP, pparam_accel, timeout) + + def set_pparameter_accel_max(self, pparam_accel_max, timeout=None): + """Command to set ID max cruise pparam accel for movement [mm/s²].""" + if self.PARAM_PVS.PPARAM_MAXACC_SP is None: + return True + else: + return self._write_sp( + self.PARAM_PVS.PPARAM_MAXACC_SP, pparam_accel_max, timeout) + + # --- kparameter --- + + @property + def kparameter_tol(self): + """KParameter position tolerance [mm].""" + if self.PARAM_PVS.KPARAM_TOL_RB is None: + return self.parameters.KPARAM_TOL + else: + return self[self.PARAM_PVS.KPARAM_TOL_RB] + + @property + def kparameter_parked(self): + """Return ID parked kparameter value [mm].""" + if self.PARAM_PVS.KPARAM_PARKED_CTE in self.properties_all: + return self[self.PARAM_PVS.KPARAM_PARKED_CTE] + else: + return self.parameters.KPARAM_PARKED + + @property + def kparameter_speed_max(self): + """Return max kparameter speed readback [mm/s].""" + return self[self.PARAM_PVS.KPARAM_MAXVELO_RB] + + @property + def kparameter_speed_max_lims(self): + """Return max kparameter speed limits.""" + ctrl = self.pv_ctrlvars(self.PARAM_PVS.KPARAM_MAXVELO_SP) lims = [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] return lims - # --- phase --- + @property + def kparameter_speed(self): + """Return kparameter speed readback [mm/s].""" + return self[self.PARAM_PVS.KPARAM_VELO_SP] @property - def phase_parked(self): - """Return ID parked phase value [mm].""" - return self.period_length / 2 + def kparameter_speed_mon(self): + """Return kparameter speed monitor [mm/s].""" + if self.PARAM_PVS.KPARAM_VELO_MON is None: + return None + else: + return self[self.PARAM_PVS.KPARAM_VELO_MON] @property - def phase(self): - """Return APU phase [mm].""" - return self['Phase-SP'] + def kparameter_accel_max(self): + """Return maximum kparameter acceleration [mm/s²].""" + if self.PARAM_PVS.KPARAM_MAXACC_RB is None: + return None + else: + return self[self.PARAM_PVS.KPARAM_MAXACC_RB] @property - def phase_min(self): - """Return ID phase lower control limit [mm].""" - ctrlvars = self.pv_ctrlvars('Phase-SP') - return ctrlvars['lower_ctrl_limit'] + def kparameter_accel_max_lims(self): + """Return max kparameter accel limits.""" + if self.PARAM_PVS.KPARAM_MAXACC_RB is None: + return None + else: + ctrl = self.pv_ctrlvars(self.PARAM_PVS.KPARAM_MAXACC_SP) + lims = [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] + return lims @property - def phase_max(self): - """Return ID phase upper control limit [mm].""" - ctrlvars = self.pv_ctrlvars('Phase-SP') - return ctrlvars['upper_ctrl_limit'] + def kparameter_accel(self): + """Return kparameter acceleration [mm/s²].""" + if self.PARAM_PVS.KPARAM_ACC_RB is None: + return None + else: + return self[self.PARAM_PVS.KPARAM_ACC_RB] @property - def phase_mon(self): - """Return APU phase [mm].""" - return self['Phase-Mon'] + def kparameter_lims(self): + """Return ID kparameter control limits [mm].""" + ctrl = self.pv_ctrlvars(self.PARAM_PVS.KPARAM_SP) + return [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] - # --- Kparam methods --- + @property + def kparameter(self): + """Return ID kparameter readback [mm].""" + return self[self.PARAM_PVS.KPARAM_RB] @property - def idkx(self): - """Return APU Kx.""" - return self['Kx-SP'] + def kparameter_mon(self): + """Return ID kparameter monitor [mm].""" + return self[self.PARAM_PVS.KPARAM_MON] - @idkx.setter - def idkx(self, value): - """Set APU Kx.""" - self['Kx-SP'] = value + @property + def kparameter_move_eta(self): + """Return estimated moving time to reach kparameter RB position.""" + # NOTE: the IOC may provide this as PV in the future + _, kparam_eta = self.calc_move_eta(None, self.kparameter) + return kparam_eta + + def set_kparameter(self, kparam, timeout=None): + """Set ID target kparameter for movement [mm].""" + return self._write_sp(self.PARAM_PVS.KPARAM_SP, kparam, timeout) + + def set_kparameter_speed(self, kparam_speed, timeout=None): + """Command to set ID cruise kparam speed for movement [mm/s].""" + return self._write_sp( + self.PARAM_PVS.KPARAM_VELO_SP, kparam_speed, timeout) + + def set_kparameter_speed_max(self, kparam_speed_max, timeout=None): + """Command to set ID max cruise kparam speed for movement [mm/s].""" + return self._write_sp( + self.PARAM_PVS.KPARAM_MAXVELO_SP, kparam_speed_max, timeout) + + def set_kparameter_accel(self, kparam_accel, timeout=None): + """Command to set ID kparam accel for movement [mm/s²].""" + if self.PARAM_PVS.KPARAM_ACC_SP is None: + return True + else: + return self._write_sp( + self.PARAM_PVS.KPARAM_ACC_SP, kparam_accel, timeout) - # --- movement checks --- + def set_kparameter_accel_max(self, kparam_accel_max, timeout=None): + """Command to set ID max cruise kparam accel for movement [mm/s²].""" + if self.PARAM_PVS.KPARAM_MAXACC_SP is None: + return True + else: + return self._write_sp( + self.PARAM_PVS.KPARAM_MAXACC_SP, kparam_accel_max, timeout) + + # --- checks --- @property def is_moving(self): """Return True if phase is changing.""" return round(self['Moving-Mon']) == 1 + @property + def is_beamline_ctrl_enabled(self): + """Return beamline control enabled state (True|False).""" + return self['BeamLineCtrlEnbl-Sts'] != 0 + # --- cmd_beamline and cmd_drive def cmd_beamline_ctrl_enable(self, timeout=None): @@ -139,98 +450,425 @@ def cmd_beamline_ctrl_disable(self, timeout=None): """Command disable bealine ID control.""" return self._write_sp('BeamLineCtrlEnbl-Sel', 0, timeout) - # --- set methods --- + # --- wait - def set_phase(self, phase, timeout=None): - """Command to set ID target phase for movement [mm].""" - return self._write_sp('Phase-SP', phase, timeout) + def wait_while_busy(self, timeout=None): + """Command wait within timeout while ID control is busy.""" + return True - def set_phase_speed(self, phase_speed, timeout=None): - """Command to set ID cruise phase speed for movement [mm/s].""" - return self._write_sp('PhaseSpeed-SP', phase_speed, timeout) + def wait_move_start(self, timeout=None): + """Wait for movement to start.""" + return self._wait('Moving-Mon', 1, timeout) - def set_phase_speed_max(self, phase_speed_max, timeout=None): - """Command to set ID max cruise phase speed for movement [mm/s].""" - return self._write_sp('MaxPhaseSpeed-SP', phase_speed_max, timeout) + def wait_move_finish(self, timeout=None): + """Wait for movement to finish.""" + return self._wait('Moving-Mon', 0, timeout) - # --- cmd_wait + def wait_move_config(self, pparam, kparam, timeout): + """.""" + tol_kparam, tol_pparam = self.kparameter_tol, self.pparameter_tol + # wait for movement within reasonable time + time_init = _time.time() + while True: + k_pos_ok = True if kparam is None else \ + abs(abs(self.kparameter_mon) - abs(kparam)) <= tol_kparam + p_pos_ok = True if pparam is None else \ + abs(self.pparameter_mon - pparam) <= tol_pparam + if p_pos_ok and k_pos_ok and not self.is_moving: + return True + if _time.time() - time_init > timeout: + print(f'tol_total: {timeout:.3f} s') + print(f'wait_time: {_time.time() - time_init:.3f} s') + print() + return False + _time.sleep(self._SHORT_SHUT_EYE) - def wait_move(self): - """Wait for phase movement to complete.""" - _time.sleep(APU._MOVECHECK_SLEEP) - while self.is_moving: - _time.sleep(APU._MOVECHECK_SLEEP) + # --- cmd_move --- - # -- cmd_move + def cmd_move_disable(self): + """Command to disable and break ID movements.""" + return True - def cmd_move_stop(self, timeout=_default_timeout): - """Send command to stop ID movement.""" - self['DevCtrl-Cmd'] = APU._CMD_MOVE_STOP + def cmd_move_enable(self): + """Command to enable ID movements.""" return True - def cmd_move_start(self, timeout=_default_timeout): - """Send command to start ID movement.""" - self['DevCtrl-Cmd'] = APU._CMD_MOVE_START + def cmd_move_abort(self): + """.""" + if self.PARAM_PVS.MOVE_ABORT is not None: + self[self.PARAM_PVS.MOVE_ABORT] = 1 + + def cmd_move_stop(self, timeout=None): + """Command to interrupt and then enable phase movements.""" + timeout = timeout or self._DEF_TIMEOUT + + # wait for not busy state + if not self.wait_while_busy(timeout=timeout): + return False + + # send abort command + self.cmd_move_abort() + + # send disable command + self.cmd_move_disable() + + # check for successful stop + if not self.wait_while_busy(timeout=timeout): + return False + if not self.wait_move_finish(timeout=timeout): + return False + + # enable movement again + if not self.cmd_move_enable(): + return False + return True - def cmd_move_park(self, timeout=None): - """Command to set and start ID movement to parked config.""" - return self.move(self.phase_parked, timeout=timeout) + def cmd_move_start(self, timeout=None): + """Command to start movement.""" + success = True + success &= self.cmd_move_pparameter_start(timeout=timeout) + success &= self.cmd_move_kparameter_start(timeout=timeout) + return success + + def cmd_move_pparameter_start(self, timeout=None): + """Command to start Pparameter movement.""" + return self._move_start( + self.PARAM_PVS.PPARAM_CHANGE_CMD, timeout=timeout) - def move(self, phase, timeout=None): - """Command to set and start phase movements.""" - # calc ETA - dtime_max = abs(phase - self.phase_mon) / self.phase_speed + def cmd_move_kparameter_start(self, timeout=None): + """Command to start Kparameter movement.""" + return self._move_start( + self.PARAM_PVS.KPARAM_CHANGE_CMD, timeout=timeout) - # additional percentual in ETA - tol_dtime = 300 # [%] - tol_factor = (1 + tol_dtime/100) - tol_total = tol_factor * dtime_max + 5 + def cmd_change_polarization_start(self, timeout=None): + """Change polarization.""" + return self._move_start( + self.PARAM_PVS.POL_CHANGE_CMD, timeout=timeout) - # set target phase and gap - if not self.set_phase(phase=phase, timeout=timeout): + def cmd_move_park(self, timeout=None): + """Move ID to parked config.""" + pparam, kparam = self.pparameter_parked, self.kparameter_parked + if self.PARAM_PVS.START_PARKING_CMD is None: + # composed pparam and kparam movement by this class + return self.cmd_move(pparam, kparam, timeout) + else: + # composed pparam and kparam movement by IOC + # first set param RBs for ETA computation and PVs consistency + if not self.set_pparameter(pparam): + return False + if not self.set_kparameter(kparam): + return False + timeout = self.calc_move_timeout(None, None, timeout) + self[self.PARAM_PVS.START_PARKING_CMD] = 1 + return self.wait_move_config(pparam, kparam, timeout) + + def cmd_move_pparameter(self, pparam=None, timeout=None): + """Command to set and start pparam movement.""" + pparam = self.pparameter if pparam is None else pparam + return self.cmd_move(pparam, None, timeout) + + def cmd_move_kparameter(self, kparam=None, timeout=None): + """Command to set and start kparam movement.""" + kparam = self.kparameter if kparam is None else kparam + return self.cmd_move(None, kparam, timeout) + + def cmd_move(self, pparam=None, kparam=None, timeout=None): + """Command to set and start pparam and kparam movements. + + Args + pparam : target pparameter value + kparam : target kparameter value + timeout : additional timeout beyond movement ETA. [s] + """ + if self.PARAM_PVS.PPARAM_SP is None: + pparam = None + + # set target pparam and kparam + t0_ = _time.time() + if pparam is not None and \ + not self.set_pparameter(pparam, timeout=timeout): + return False + if kparam is not None and \ + not self.set_kparameter(kparam, timeout=timeout): return False + t1_ = _time.time() + if timeout is not None: + timeout = max(timeout - (t1_ - t0_), 0) # command move start - if not self.cmd_move_start(timeout=timeout): + t0_ = _time.time() + if pparam is not None and \ + not self.cmd_move_pparameter_start(timeout=timeout): return False + if kparam is not None and \ + not self.cmd_move_kparameter_start(timeout=timeout): + return False + t1_ = _time.time() + if timeout is not None: + timeout = max(timeout - (t1_ - t0_), 0) - # wait for movement within reasonable time - time_init = _time.time() - while \ - abs(self.phase_mon - phase) > self.TOLERANCE_PHASE or \ - self.is_moving: - if _time.time() - time_init > tol_total: - print(f'tol_total: {tol_total:.3f} s') - print(f'wait_time: {_time.time() - time_init:.3f} s') - print() - return False - _time.sleep(self._SHORT_SHUT_EYE) + # calc timeout + timeout = self.calc_move_timeout(pparam, kparam, timeout) - # successfull movement at this point - return True + # wait for movement within timeout based on movement ETA + return self.wait_move_config(pparam, kparam, timeout) + + def cmd_change_polarization(self, polarization, timeout=None): + """.""" + if self.PARAM_PVS.POL_SEL not in self.properties_all: + return True + if self.PARAM_PVS.POL_MON not in self.properties_all: + return True + + t0_ = _time.time() + + # set desired polarization + if not self._write_sp( + self.PARAM_PVS.POL_SEL, polarization, timeout=timeout): + return False + t1_ = _time.time() + timeout = max(0, timeout - (t1_ - t0_)) + + # send change polarization command + if not self.cmd_change_polarization_start(timeout=timeout): + return False + t2_ = _time.time() + timeout = max(0, timeout - (t2_ - t0_)) + + # wait for polarization value within timeout + return self._wait( + self.PARAM_PVS.POL_MON, polarization, timeout=timeout, comp='eq') + + def calc_move_eta(self, pparam_goal=None, kparam_goal=None): + """Estimate moving time for each parameter separately.""" + # pparameter + param_goal, param_val = pparam_goal, self.pparameter_mon + param_tol = self.pparameter_tol + param_vel, param_acc = self.pparameter_speed, self.pparameter_accel + if None not in (param_goal, param_val): + dparam = abs(param_goal - param_val) + dparam = 0 if dparam < param_tol else dparam + pparam_eta = IDBase._calc_move_eta_model( + dparam, param_vel, param_acc) + else: + pparam_eta = 0.0 + + # kparameter + param_goal, param_val = kparam_goal, self.kparameter_mon + param_tol = self.kparameter_tol + param_vel, param_acc = self.kparameter_speed, self.kparameter_accel + if None not in (param_goal, param_val): + dparam = abs(abs(param_goal) - abs(param_val)) # abs for DELTA + dparam = 0 if dparam < param_tol else dparam + kparam_eta = IDBase._calc_move_eta_model( + dparam, param_vel, param_acc) + else: + kparam_eta = 0.0 + + return pparam_eta, kparam_eta + + def calc_move_eta_composed(self, pparam_eta, kparam_eta): + """.""" + # model: here pparam and kparam as serial in time + eta = pparam_eta + kparam_eta + return eta + + def calc_move_timeout( + self, pparam_goal=None, kparam_goal=None, timeout=None): + """.""" + # calc timeout + pparam_eta, kparam_eta = self.calc_move_eta(pparam_goal, kparam_goal) + eta = self.calc_move_eta_composed(pparam_eta, kparam_eta) + eta = 1.1 * eta + 0.5 # add safety margins + timeout = eta if timeout is None else eta + timeout + return timeout # --- private methods --- - def _write_sp(self, propties_sp, values, timeout=None): - timeout = timeout or self._default_timeout + def _move_start(self, cmd_propty, timeout=None, cmd_value=1): + timeout = timeout or self._DEF_TIMEOUT + + # wait for not busy state + if not self.wait_while_busy(timeout=timeout): + return False + + if cmd_propty in self.properties_all: + # send move command + self[cmd_propty] = cmd_value + + return True + + def _write_sp(self, propties_sp, values, timeout=None, pvs_sp_rb=None): + timeout = timeout or self._DEF_TIMEOUT if isinstance(propties_sp, str): propties_sp = (propties_sp, ) values = (values, ) success = True for propty_sp, value in zip(propties_sp, values): - if propty_sp in ('Phase-SP', 'PhaseSpeed-SP'): + if pvs_sp_rb is not None and propty_sp in pvs_sp_rb: + # pv is unique for SP and RB variables. propty_rb = propty_sp else: propty_rb = \ propty_sp.replace('-SP', '-RB').replace('-Sel', '-Sts') self[propty_sp] = value - success &= super()._wait( - propty_rb, value, timeout=timeout, comp='eq') + if isinstance(value, float): + success &= super()._wait_float( + propty_rb, value, timeout=timeout) + else: + success &= super()._wait( + propty_rb, value, timeout=timeout, comp='eq') return success + def _wait_propty(self, propty, value, timeout=None): + """.""" + t0_ = _time.time() + timeout = timeout if timeout is not None else self._DEF_TIMEOUT + while self[propty] != value: + _time.sleep(self._SHORT_SHUT_EYE) + if _time.time() - t0_ > timeout: + return False + return True + + @staticmethod + def _calc_move_eta_model(dparam, param_vel, param_acc=None): + """Moving time model.""" + # constant acceleration model: + # linear ramp up + cruise velocity + linear ramp down + # assume param_acc = 1 mm/s² if no acceleration is provided + param_acc = 1 if param_acc is None else param_acc + maxvel_ramp = _np.sqrt(param_acc * dparam) + maxvel_ramp = min(maxvel_ramp, param_vel) + dtime_ramp = 2 * maxvel_ramp / param_acc + dparam_ramp = maxvel_ramp**2 / param_acc + dparam_plateau = dparam - dparam_ramp + dtime_plateau = dparam_plateau / param_vel + dtime_total = dtime_ramp + dtime_plateau + return dtime_total + + +class APU(IDBase): + """APU Insertion Device.""" + + class DEVICES: + """Device names.""" + + APU22_06SB = 'SI-06SB:ID-APU22' + APU22_07SP = 'SI-07SP:ID-APU22' + APU22_08SB = 'SI-08SB:ID-APU22' + APU22_09SA = 'SI-09SA:ID-APU22' + APU58_11SP = 'SI-11SP:ID-APU58' + ALL = ( + APU22_06SB, APU22_07SP, APU22_08SB, APU22_09SA, APU58_11SP, ) + + _CMD_MOVE_STOP, _CMD_MOVE_START = 1, 3 + _CMD_MOVE = 3 + + # --- PARAM_PVS --- + PARAM_PVS = _PARAM_PVS() + PARAM_PVS.KPARAM_SP = 'Phase-SP' + PARAM_PVS.KPARAM_RB = 'Phase-SP' # There is no Phase-RB! + PARAM_PVS.KPARAM_MON = 'Phase-Mon' + PARAM_PVS.KPARAM_MAXVELO_SP = 'MaxPhaseSpeed-SP' + PARAM_PVS.KPARAM_MAXVELO_RB = 'MaxPhaseSpeed-RB' + PARAM_PVS.KPARAM_VELO_SP = 'PhaseSpeed-SP' + PARAM_PVS.KPARAM_VELO_RB = 'PhaseSpeed-SP' + PARAM_PVS.KPARAM_VELO_MON = 'PhaseSpeed-Mon' + PARAM_PVS.KPARAM_CHANGE_CMD = 'DevCtrl-Cmd' + + PROPERTIES_DEFAULT = \ + tuple(set(value for key, value in _inspect.getmembers(PARAM_PVS) \ + if not key.startswith('_') and value is not None)) + + def __init__(self, devname, props2init='all', auto_monitor_mon=True): + """.""" + # check if device exists + if devname not in APU.DEVICES.ALL: + raise NotImplementedError(devname) + + # call base class constructor + super().__init__( + devname, props2init=props2init, auto_monitor_mon=auto_monitor_mon) + + # --- phase speeds ---- + + @property + def phase_speed_max(self): + """Return max phase speed readback [mm/s].""" + return self.kparameter_speed_max + + @property + def phase_speed_max_lims(self): + """Return max phase speed limits.""" + return self.kparameter_speed_max_lims + + @property + def phase_speed(self): + """Return phase speed [mm/s].""" + return self.kparameter_speed + + @property + def phase_speed_mon(self): + """Return phase speed monitor [mm/s].""" + return self.kparameter_speed_mon + + # --- phase --- + + @property + def phase_parked(self): + """Return ID parked phase value [mm].""" + return self.kparameter_parked -class PAPU(_Device): + @property + def phase_lims(self): + """Return ID phase lower control limit [mm].""" + return self.kparameter_lims + + @property + def phase(self): + """Return APU phase [mm].""" + return self.kparameter + + @property + def phase_mon(self): + """Return APU phase [mm].""" + return self.kparameter_mon + + # --- set methods --- + + def set_phase(self, phase, timeout=None): + """Command to set ID target phase for movement [mm].""" + return self.set_kparameter(phase, timeout) + + def set_phase_speed(self, phase_speed, timeout=None): + """Command to set ID cruise phase speed for movement [mm/s].""" + return self.set_kparameter_speed(phase_speed, timeout) + + def set_phase_speed_max(self, phase_speed_max, timeout=None): + """Command to set ID max cruise phase speed for movement [mm/s].""" + return self._write_sp('MaxPhaseSpeed-SP', phase_speed_max, timeout) + + # -- cmd_move + + def cmd_move_stop(self, timeout=None): + """Send command to stop ID movement.""" + self['DevCtrl-Cmd'] = self._CMD_MOVE_STOP + return True + + # --- private methods --- + + def _write_sp(self, propties_sp, values, timeout=None): + pvs_sp_rb = ('Phase-SP', 'PhaseSpeed-SP') + return super()._write_sp( + propties_sp, values, timeout=timeout, pvs_sp_rb=pvs_sp_rb) + + def _move_start( + self, cmd_propty, timeout=None, cmd_value=_CMD_MOVE_START): + return super()._move_start(cmd_propty, timeout, cmd_value) + + +class PAPU(IDBase): """PAPU Insertion Device.""" class DEVICES: @@ -239,30 +877,35 @@ class DEVICES: PAPU50_17SA = 'SI-17SA:ID-PAPU50' ALL = (PAPU50_17SA, ) - TOLERANCE_PHASE = 0.01 # [mm] - - _SHORT_SHUT_EYE = 0.1 # [s] - _default_timeout = 8 # [s] + # --- PARAM_PVS --- + PARAM_PVS = _PARAM_PVS() + PARAM_PVS.PERIOD_LEN_CTE = 'PeriodLength-Cte' + PARAM_PVS.KPARAM_SP = 'Phase-SP' + PARAM_PVS.KPARAM_RB = 'Phase-RB' + PARAM_PVS.KPARAM_MON = 'Phase-Mon' + PARAM_PVS.KPARAM_PARKED_CTE = 'ParkedPhase-Cte' + PARAM_PVS.KPARAM_MAXVELO_SP = 'MaxPhaseSpeed-SP' + PARAM_PVS.KPARAM_MAXVELO_RB = 'MaxPhaseSpeed-RB' + PARAM_PVS.KPARAM_VELO_SP = 'PhaseSpeed-SP' + PARAM_PVS.KPARAM_VELO_RB = 'PhaseSpeed-RB' + PARAM_PVS.KPARAM_VELO_MON = 'PhaseSpeed-Mon' + PARAM_PVS.KPARAM_CHANGE_CMD = 'ChangePhase-Cmd' - _properties_papu = ( - 'Home-Cmd', 'EnblPwrPhase-Cmd', 'ClearErr-Cmd', - 'BeamLineCtrl-Mon', # 'Home-Mon', - ) _properties = ( - 'PeriodLength-Cte', - 'BeamLineCtrlEnbl-Sel', 'BeamLineCtrlEnbl-Sts', - 'Moving-Mon', 'PwrPhase-Mon', 'EnblAndReleasePhase-Sel', 'EnblAndReleasePhase-Sts', 'AllowedToChangePhase-Mon', - 'ParkedPhase-Cte', - 'Phase-SP', 'Phase-RB', 'Phase-Mon', - 'PhaseSpeed-SP', 'PhaseSpeed-RB', 'PhaseSpeed-Mon', - 'MaxPhaseSpeed-SP', 'MaxPhaseSpeed-RB', - 'StopPhase-Cmd', 'ChangePhase-Cmd', - 'Log-Mon', + 'StopPhase-Cmd', 'Log-Mon', ) - PROPERTIES_DEFAULT = _properties + _properties_papu + _properties_papu = ( + 'Home-Cmd', 'EnblPwrPhase-Cmd', 'ClearErr-Cmd', + 'BeamLineCtrl-Mon', + ) + + PROPERTIES_DEFAULT = \ + tuple(set(value for key, value in _inspect.getmembers(PARAM_PVS) \ + if not key.startswith('_') and value is not None)) + \ + _properties + _properties_papu def __init__(self, devname=None, props2init='all', auto_monitor_mon=True): """.""" @@ -276,11 +919,6 @@ def __init__(self, devname=None, props2init='all', auto_monitor_mon=True): super().__init__( devname, props2init=props2init, auto_monitor_mon=auto_monitor_mon) - @property - def period_length(self): - """Return ID period length [mm].""" - return self['PeriodLength-Cte'] - @property def log_mon(self): """Return ID Log.""" @@ -288,56 +926,47 @@ def log_mon(self): # --- phase speeds ---- - @property - def phase_speed(self): - """Return phase speed readback [mm/s].""" - return self['PhaseSpeed-RB'] - - @property - def phase_speed_mon(self): - """Return phase speed monitor [mm/s].""" - return self['PhaseSpeed-Mon'] - @property def phase_speed_max(self): """Return max phase speed readback [mm/s].""" - return self['MaxPhaseSpeed-RB'] + return self.kparameter_speed_max @property def phase_speed_max_lims(self): """Return max phase speed limits.""" - ctrl = self.pv_ctrlvars('MaxPhaseSpeed-SP') - lims = [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] - return lims + return self.kparameter_speed_max_lims + + @property + def phase_speed(self): + """Return phase speed readback [mm/s].""" + return self.kparameter_speed + + @property + def phase_speed_mon(self): + """Return phase speed monitor [mm/s].""" + return self.kparameter_speed_mon # --- phase --- @property def phase_parked(self): """Return ID parked phase value [mm].""" - return self['ParkedPhase-Cte'] + return self.kparameter_parked @property - def phase(self): - """Return ID phase readback [mm].""" - return self['Phase-RB'] + def phase_lims(self): + """Return ID phase limits [mm].""" + return self.kparameter_lims @property - def phase_min(self): - """Return ID phase lower control limit [mm].""" - ctrlvars = self.pv_ctrlvars('Phase-SP') - return ctrlvars['lower_ctrl_limit'] - - @property - def phase_max(self): - """Return ID phase upper control limit [mm].""" - ctrlvars = self.pv_ctrlvars('Phase-SP') - return ctrlvars['upper_ctrl_limit'] + def phase(self): + """Return ID phase readback [mm].""" + return self.kparameter @property def phase_mon(self): """Return ID phase monitor [mm].""" - return self['Phase-Mon'] + return self.kparameter_mon # --- drive checks --- @@ -356,19 +985,14 @@ def is_move_phase_enabled(self): @property def is_moving(self): """Return is moving state (True|False).""" - return self['Moving-Mon'] != 0 + return self[self.PARAM_PVS.IS_MOVING] != 0 @property def is_homing(self): """Return whether ID is in homing procedure (True|False).""" return self['Home-Mon'] != 0 - @property - def is_beamline_ctrl_enabled(self): - """Return beamline control enabled state (True|False).""" - return self['BeamLineCtrlEnbl-Sts'] != 0 - - # --- cmd_beamline and cmd_drive + # --- cmd_drive def cmd_drive_turn_power_on(self, timeout=None): """Command turn phase drive on.""" @@ -378,27 +1002,19 @@ def cmd_drive_turn_power_on(self, timeout=None): props_values = {'PwrPhase-Mon': 1} return self._wait_propty_values(props_values, timeout=timeout) - def cmd_beamline_ctrl_enable(self, timeout=None): - """Command enable bealine ID control.""" - return self._write_sp('BeamLineCtrlEnbl-Sel', 1, timeout) - - def cmd_beamline_ctrl_disable(self, timeout=None): - """Command disable bealine ID control.""" - return self._write_sp('BeamLineCtrlEnbl-Sel', 0, timeout) - # --- set methods --- def set_phase(self, phase, timeout=None): """Command to set ID target phase for movement [mm].""" - return self._write_sp('Phase-SP', phase, timeout) + return self.set_kparameter(phase, timeout) def set_phase_speed(self, phase_speed, timeout=None): """Command to set ID cruise phase speed for movement [mm/s].""" - return self._write_sp('PhaseSpeed-SP', phase_speed, timeout) + return self.set_kparameter_speed(phase_speed, timeout) def set_phase_speed_max(self, phase_speed_max, timeout=None): """Command to set ID max cruise phase speed for movement [mm/s].""" - return self._write_sp('MaxPhaseSpeed-SP', phase_speed_max, timeout) + return self.set_kparameter_speed_max(phase_speed_max, timeout) # --- cmd_move disable/enable --- @@ -424,94 +1040,11 @@ def cmd_move_disable(self, timeout=None): """Command to disable and break ID phase and gap movements.""" return self.cmd_move_phase_disable(timeout=timeout) - # --- cmd_wait - - def wait_while_busy(self, timeout=None): - """Command wait within timeout while ID control is busy.""" - return True - - def wait_move_start(self, timeout=None): - """Command wait until movement starts or timeout.""" - time_init = _time.time() - while not self.is_moving: - if timeout is not None and _time.time() - time_init > timeout: - return False - return True - # -- cmd_move - def cmd_move_stop(self, timeout=None): - """Command to interrupt and then enable phase movements.""" - timeout = timeout or self._default_timeout - - # wait for not busy state - if not self.wait_while_busy(timeout=timeout): - return False - - # send stop command - self.cmd_move_disable() - - # check for successful stop - if not self.wait_while_busy(timeout=timeout): - return False - - success = True - success &= super()._wait('Moving-Mon', 0, timeout=timeout) - - if not success: - return False - - # enable movement again - status = self.cmd_move_enable(timeout=timeout) - - return status - - def cmd_move_start(self, timeout=None): - """Command to start movement.""" - success = True - success &= self.cmd_move_phase_start(timeout=timeout) - return success - def cmd_move_phase_start(self, timeout=None): """Command to start phase movement.""" - return self._move_start('ChangePhase-Cmd', timeout=timeout) - - def cmd_move_park(self, timeout=None): - """Command to set and start ID movement to parked config.""" - return self.move(self.phase_parked, timeout=timeout) - - def move(self, phase, timeout=None): - """Command to set and start phase movements.""" - # calc ETA - dtime_max = abs(phase - self.phase_mon) / self.phase_speed - - # additional percentual in ETA - tol_dtime = 300 # [%] - tol_factor = (1 + tol_dtime/100) - tol_total = tol_factor * dtime_max + 5 - - # set target phase and gap - if not self.set_phase(phase=phase, timeout=timeout): - return False - - # command move start - if not self.cmd_move_phase_start(timeout=timeout): - return False - - # wait for movement within reasonable time - time_init = _time.time() - while \ - abs(self.phase_mon - phase) > self.TOLERANCE_PHASE or \ - self.is_moving: - if _time.time() - time_init > tol_total: - print(f'tol_total: {tol_total:.3f} s') - print(f'wait_time: {_time.time() - time_init:.3f} s') - print() - return False - _time.sleep(self._SHORT_SHUT_EYE) - - # successfull movement at this point - return True + return self.cmd_move_kparameter_start(timeout=timeout) # --- cmd_reset @@ -529,49 +1062,6 @@ def cmd_clear_error(self): """Command to clear errors.""" self['ClearErr-Cmd'] = 1 - # --- private methods --- - - def _move_start(self, cmd_propty, timeout=None): - timeout = timeout or self._default_timeout - - # wait for not busy state - if not self.wait_while_busy(timeout=timeout): - return False - - # send move command - self[cmd_propty] = 1 - - return True - - def _write_sp(self, propties_sp, values, timeout=None): - timeout = timeout or self._default_timeout - if isinstance(propties_sp, str): - propties_sp = (propties_sp, ) - values = (values, ) - success = True - for propty_sp, value in zip(propties_sp, values): - propty_rb = propty_sp.replace('-SP', '-RB').replace('-Sel', '-Sts') - # if self[propty_rb] == value: - # continue - if not self.wait_while_busy(timeout=timeout): - return False - self[propty_sp] = value - success &= super()._wait( - propty_rb, value, timeout=timeout, comp='eq') - return success - - def _wait_propty_values(self, props_values, timeout=None, comp='eq'): - timeout = timeout if timeout is not None else self._default_timeout - time0 = _time.time() - for propty, value in props_values.items(): - timeout_left = max(0, timeout - (_time.time() - time0)) - if timeout_left == 0: - return False - if not super()._wait( - propty, value, timeout=timeout_left, comp=comp): - return False - return True - class EPU(PAPU): """EPU Insertion Device.""" @@ -582,17 +1072,42 @@ class DEVICES: EPU50_10SB = 'SI-10SB:ID-EPU50' ALL = (EPU50_10SB, ) - PROPERTIES_DEFAULT = PAPU._properties + ( - 'EnblPwrAll-Cmd', - 'PwrGap-Mon', - 'Status-Mon', + # --- PARAM_PVS --- + PARAM_PVS = _PARAM_PVS() + PARAM_PVS.PERIOD_LEN_CTE = 'PeriodLength-Cte' + PARAM_PVS.IS_MOVING = 'IsMoving-Mon' + PARAM_PVS.PPARAM_SP = 'Phase-SP' + PARAM_PVS.PPARAM_RB = 'Phase-RB' + PARAM_PVS.PPARAM_MON = 'Phase-Mon' + PARAM_PVS.PPARAM_PARKED_CTE = 'ParkedPhase-Cte' + PARAM_PVS.PPARAM_MAXVELO_SP = 'MaxPhaseSpeed-SP' + PARAM_PVS.PPARAM_MAXVELO_RB = 'MaxPhaseSpeed-RB' + PARAM_PVS.PPARAM_VELO_SP = 'PhaseSpeed-SP' + PARAM_PVS.PPARAM_VELO_RB = 'PhaseSpeed-RB' + PARAM_PVS.PPARAM_VELO_MON = 'PhaseSpeed-Mon' + PARAM_PVS.PPARAM_CHANGE_CMD = 'ChangePhase-Cmd' + PARAM_PVS.KPARAM_SP = 'Gap-SP' + PARAM_PVS.KPARAM_RB = 'Gap-RB' + PARAM_PVS.KPARAM_MON = 'Gap-Mon' + PARAM_PVS.KPARAM_PARKED_CTE = 'ParkedGap-Cte' + PARAM_PVS.KPARAM_MAXVELO_SP = 'MaxGapSpeed-SP' + PARAM_PVS.KPARAM_MAXVELO_RB = 'MaxGapSpeed-RB' + PARAM_PVS.KPARAM_VELO_SP = 'GapSpeed-SP' + PARAM_PVS.KPARAM_VELO_RB = 'GapSpeed-RB' + PARAM_PVS.KPARAM_VELO_MON = 'GapSpeed-Mon' + PARAM_PVS.KPARAM_CHANGE_CMD = 'ChangeGap-Cmd' + PARAM_PVS.POL_SEL = 'Polarization-Sel' + PARAM_PVS.POL_STS = 'Polarization-Sts' + PARAM_PVS.POL_MON = 'Polarization-Mon' + PARAM_PVS.POL_CHANGE_CMD = 'ChangePolarization-Cmd' + + PROPERTIES_DEFAULT = PAPU._properties + \ + tuple(set(value for key, value in _inspect.getmembers(PARAM_PVS) \ + if not key.startswith('_') and value is not None)) + ( + 'EnblPwrAll-Cmd', 'PwrGap-Mon', 'Status-Mon', 'EnblAndReleaseGap-Sel', 'EnblAndReleaseGap-Sts', 'AllowedToChangeGap-Mon', - 'ParkedGap-Cte', 'IsBusy-Mon', - 'Gap-SP', 'Gap-RB', 'Gap-Mon', - 'GapSpeed-SP', 'GapSpeed-RB', 'GapSpeed-Mon', - 'MaxGapSpeed-SP', 'MaxGapSpeed-RB', - 'ChangeGap-Cmd', 'Stop-Cmd', + 'IsBusy-Mon', 'Stop-Cmd', ) def __init__(self, devname=None, props2init='all', auto_monitor_mon=True): @@ -614,56 +1129,47 @@ def status(self): # --- gap speeds ---- - @property - def gap_parked(self): - """Return ID parked gap value [mm].""" - return self['ParkedGap-Cte'] - @property def gap_speed(self): """Return gap speed readback [mm/s].""" - return self['GapSpeed-RB'] + return self.kparameter_speed @property def gap_speed_mon(self): """Return gap speed monitor [mm/s].""" - return self['GapSpeed-Mon'] + return self.kparameter_speed_mon @property def gap_speed_max(self): """Return max gap speed readback [mm/s].""" - return self['MaxGapSpeed-RB'] + return self.kparameter_speed_max @property def gap_speed_max_lims(self): """Return max gap speed limits.""" - ctrl = self.pv_ctrlvars('MaxGapSpeed-SP') - lims = [ctrl['lower_ctrl_limit'], ctrl['upper_ctrl_limit']] - return lims + return self.kparameter_speed_max_lims # --- gap --- @property - def gap(self): - """Return ID gap readback [mm].""" - return self['Gap-RB'] + def gap_parked(self): + """Return ID parked gap value [mm].""" + return self.kparameter_parked @property - def gap_min(self): - """Return ID gap lower control limit [mm].""" - ctrlvars = self.pv_ctrlvars('Gap-SP') - return ctrlvars['lower_ctrl_limit'] + def gap(self): + """Return ID gap readback [mm].""" + return self.kparameter @property - def gap_max(self): - """Return ID gap upper control limit [mm].""" - ctrlvars = self.pv_ctrlvars('Gap-SP') - return ctrlvars['upper_ctrl_limit'] + def gap_lims(self): + """Return ID gap control limits [mm].""" + return self.kparameter_lims @property def gap_mon(self): """Return ID gap monitor [mm].""" - return self['Gap-Mon'] + return self.kparameter_mon # --- drive checks --- @@ -709,21 +1215,21 @@ def cmd_drive_turn_power_on(self, timeout=None): return True self['EnblPwrAll-Cmd'] = 1 props_values = {'PwrPhase-Mon': 1, 'PwrGap-Mon': 1} - return self._wait_propty_values(props_values, timeout=timeout) + return self._wait_set(props_values, timeout=timeout) # --- set methods --- def set_gap(self, gap, timeout=None): - """Command to set ID target gap for movement [mm].""" - return self._write_sp('Gap-SP', gap, timeout) + """Set ID target gap for movement [mm].""" + return self.set_kparameter(gap, timeout) def set_gap_speed(self, gap_speed, timeout=None): - """Command to set ID cruise gap speed for movement [mm/s].""" - return self._write_sp('GapSpeed-SP', gap_speed, timeout) + """Set ID cruise gap speed for movement [mm/s].""" + return self.set_kparameter_speed(gap_speed, timeout) def set_gap_speed_max(self, gap_speed_max, timeout=None): - """Command to set ID max cruise gap speed for movement [mm/s].""" - return self._write_sp('MaxGapSpeed-SP', gap_speed_max, timeout) + """Set ID max cruise gap speed for movement [mm/s].""" + return self.set_kparameter_speed_max(gap_speed_max, timeout) # --- cmd_move disable/enable --- @@ -759,7 +1265,7 @@ def cmd_move_disable(self, timeout=None): def wait_while_busy(self, timeout=None): """Command wait within timeout while ID control is busy.""" - timeout = timeout or self._default_timeout + timeout = timeout or self._DEF_TIMEOUT time_init = _time.time() while self.is_busy: _time.sleep(min(self._SHORT_SHUT_EYE, timeout)) @@ -769,63 +1275,14 @@ def wait_while_busy(self, timeout=None): # -- cmd_move - def cmd_move_start(self, timeout=None): - """Command to start movement.""" - success = True - success &= self.cmd_move_gap_start(timeout=timeout) - success &= self.cmd_move_phase_start(timeout=timeout) - return success - def cmd_move_gap_start(self, timeout=None): """Command to start gap movement.""" - return self._move_start('ChangeGap-Cmd', timeout=timeout) - - def cmd_move_park(self, timeout=None): - """Command to set and start ID movement to parked config.""" - return self.move( - self.phase_parked, self.gap_parked, timeout=timeout) - - def move(self, phase, gap, timeout=None): - """Command to set and start phase and gap movements.""" - # calc ETA - dtime_phase = abs(phase - self.phase_mon) / self.phase_speed - dtime_gap = abs(gap - self.gap_mon) / self.gap_speed - dtime_max = max(dtime_phase, dtime_gap) - - # additional percentual in ETA - tol_gap = 0.01 # [mm] - tol_phase = 0.01 # [mm] - tol_dtime = 300 # [%] - tol_factor = (1 + tol_dtime/100) - tol_total = tol_factor * dtime_max + 5 - - # set target phase and gap - if not self.set_phase(phase=phase, timeout=timeout): - return False - if not self.set_gap(gap=gap, timeout=timeout): - return False - - # command move start - if not self.cmd_move_phase_start(timeout=timeout): - return False - if not self.cmd_move_gap_start(timeout=timeout): - return False - - # wait for movement within reasonable time - time_init = _time.time() - while \ - abs(self.gap_mon - gap) > tol_gap or \ - abs(self.phase_mon - phase) > tol_phase or \ - self.is_moving: - if _time.time() - time_init > tol_total: - print(f'tol_total: {tol_total:.3f} s') - print(f'wait_time: {_time.time() - time_init:.3f} s') - print() - return False - _time.sleep(EPU._SHORT_SHUT_EYE) + return self.cmd_move_kparameter_start(timeout) - # successfull movement at this point - return True + def calc_move_eta_composed(self, pparam_eta, kparam_eta): + # model: here pparam and kparam as parallel in time + eta = max(pparam_eta, kparam_eta) + return eta # --- other cmds --- @@ -834,7 +1291,145 @@ def cmd_clear_error(self): pass -class WIG(_Device): +class DELTA(IDBase): + """DELTA Insertion Device.""" + + class DEVICES: + """Device names.""" + + DELTA52_10SB = 'SI-10SB:ID-DELTA52' + ALL = (DELTA52_10SB, ) + + # --- PARAM_PVS --- + PARAM_PVS = _PARAM_PVS() + PARAM_PVS.PERIOD_LEN_CTE = 'PeriodLength-Cte' + PARAM_PVS.IS_MOVING = 'Moving-Mon' + PARAM_PVS.START_PARKING_CMD = 'StartParking-Cmd' + PARAM_PVS.MOVE_ABORT = 'Abort-Cmd' + PARAM_PVS.PPARAM_SP = 'PParam-SP' + PARAM_PVS.PPARAM_RB = 'PParam-RB' + PARAM_PVS.PPARAM_MON = 'PParam-Mon' + PARAM_PVS.PPARAM_PARKED_CTE = 'PParamParked-Cte' + PARAM_PVS.PPARAM_MAXACC_SP = 'MaxAcc-SP' + PARAM_PVS.PPARAM_MAXACC_RB = 'MaxAcc-RB' + PARAM_PVS.PPARAM_MAXVELO_SP = 'MaxVelo-SP' + PARAM_PVS.PPARAM_MAXVELO_RB = 'MaxVelo-RB' + PARAM_PVS.PPARAM_VELO_SP = 'PParamVelo-SP' + PARAM_PVS.PPARAM_VELO_RB = 'PParamVelo-RB' + PARAM_PVS.PPARAM_ACC_SP = 'PParamAcc-SP' + PARAM_PVS.PPARAM_ACC_RB = 'PParamAcc-RB' + PARAM_PVS.PPARAM_TOL_SP = 'PolTol-SP' + PARAM_PVS.PPARAM_TOL_RB = 'PolTol-RB' + PARAM_PVS.PPARAM_CHANGE_CMD = 'PParamChange-Cmd' + PARAM_PVS.KPARAM_SP = 'KParam-SP' + PARAM_PVS.KPARAM_RB = 'KParam-RB' + PARAM_PVS.KPARAM_MON = 'KParam-Mon' + PARAM_PVS.KPARAM_PARKED_CTE = 'KParamParked-Cte' + PARAM_PVS.KPARAM_MAXACC_SP = 'MaxAcc-SP' + PARAM_PVS.KPARAM_MAXACC_RB = 'MaxAcc-RB' + PARAM_PVS.KPARAM_MAXVELO_SP = 'MaxVelo-SP' + PARAM_PVS.KPARAM_MAXVELO_RB = 'MaxVelo-RB' + PARAM_PVS.KPARAM_VELO_SP = 'KParamVelo-SP' + PARAM_PVS.KPARAM_VELO_RB = 'KParamVelo-RB' + PARAM_PVS.KPARAM_ACC_SP = 'KParamAcc-SP' + PARAM_PVS.KPARAM_ACC_RB = 'KParamAcc-RB' + PARAM_PVS.KPARAM_TOL_SP = 'PosTol-SP' + PARAM_PVS.KPARAM_TOL_RB = 'PosTol-RB' + PARAM_PVS.KPARAM_CHANGE_CMD = 'KParamChange-Cmd' + PARAM_PVS.POL_SEL = 'Pol-Sel' + PARAM_PVS.POL_STS = 'Pol-Sts' + PARAM_PVS.POL_MON = 'Pol-Mon' + PARAM_PVS.POL_CHANGE_CMD = 'PolChange-Cmd' + + PROPERTIES_DEFAULT = tuple(set( + value for key, value in _inspect.getmembers(PARAM_PVS) + if not key.startswith('_') and value is not None)) + PROPERTIES_DEFAULT = PROPERTIES_DEFAULT + ( + 'CSDVirtPos-Mon', 'CSEVirtPos-Mon', + 'CIEVirtPos-Mon', 'CIDVirtPos-Mon', + 'IsOperational-Mon', 'MotorsEnbld-Mon', + 'Alarm-Mon', 'Intlk-Mon', 'IntlkBits-Mon', 'IntlkLabels-Cte', + 'ConsistentSetPoints-Mon', 'PLCState-Mon', + 'Energy-SP', 'Energy-RB', 'Energy-Mon', + 'KValue-SP', 'KValue-RB', 'KValue-Mon', + ) + + def __init__(self, devname=None, props2init='all', auto_monitor_mon=True): + """.""" + # check if device exists + if devname is None: + devname = self.DEVICES.DELTA52_10SB + if devname not in self.DEVICES.ALL: + raise NotImplementedError(devname) + + # call base class constructor + super().__init__( + devname, props2init=props2init, auto_monitor_mon=auto_monitor_mon) + + @property + def is_operational(self): + """Return True if ID is operational.""" + return self['IsOperational-Mon'] == 0 # 0 : 'OK' + + # --- cassette positions --- + + @property + def pos_csd_mon(self): + """Return longitudinal position of CSD [mm]. + + cassette positions x (PParam, KParam): + pos_cid = PParam + pos_cse = PParam + KParam + pos_csd = KParam + pos_cie = 0 + """ + return self['CSDVirtPos-Mon'] + + @property + def pos_cse_mon(self): + """Return longitudinal position of CSE [mm]. + + cassette positions x (PParam, KParam): + pos_cid = PParam + pos_cse = PParam + KParam + pos_csd = KParam + pos_cie = 0 + """ + return self['CSEVirtPos-Mon'] + + @property + def pos_cie_mon(self): + """Return longitudinal position of CIE [mm]. + + cassette positions x (PParam, KParam): + pos_cid = PParam + pos_cse = PParam + KParam + pos_csd = KParam + pos_cie = 0 + """ + return self['CIEVirtPos-Mon'] + + @property + def pos_cid_mon(self): + """Return longitudinal position of CID [mm]. + + cassette positions x (PParam, KParam): + pos_cid = PParam + pos_cse = PParam + KParam + pos_csd = KParam + pos_cie = 0 + """ + return self['CIDVirtPos-Mon'] + + def calc_move_eta(self, pparam_goal=None, kparam_goal=None): + """Estimate moving time for each parameter separately.""" + pol_mon_str = self.polarization_mon_str + if kparam_goal is not None and pol_mon_str == 'circularp': + kparam_goal = -kparam_goal + return super().calc_move_eta(pparam_goal, kparam_goal) + + +class WIG(IDBase): """Wiggler Insertion Device.""" class DEVICES: @@ -857,3 +1452,40 @@ def __init__(self, devname=None, props2init='all', auto_monitor_mon=True): # call base class constructor super().__init__( devname, props2init=props2init, auto_monitor_mon=auto_monitor_mon) + + +class ID(IDBase): + """Insertion Device.""" + + class DEVICES: + APU = APU.DEVICES + PAPU = PAPU.DEVICES + EPU = EPU.DEVICES + DELTA = DELTA.DEVICES + WIG = WIG.DEVICES + ALL = APU.ALL + PAPU.ALL + \ + EPU.ALL + DELTA.ALL + WIG.ALL + + def __new__(cls, devname, **kwargs): + """.""" + IDClass = ID.get_idclass(devname) + if IDClass: + return IDClass(devname, **kwargs) + else: + raise NotImplementedError(devname) + + @staticmethod + def get_idclass(devname): + """.""" + if devname in APU.DEVICES.ALL: + return APU + elif devname in PAPU.DEVICES.ALL: + return PAPU + elif devname in EPU.DEVICES.ALL: + return EPU + elif devname in DELTA.DEVICES.ALL: + return DELTA + elif devname in WIG.DEVICES.ALL: + return WIG + else: + return None diff --git a/siriuspy/siriuspy/devices/injsys.py b/siriuspy/siriuspy/devices/injsys.py index df1471aca..aa42b05a5 100644 --- a/siriuspy/siriuspy/devices/injsys.py +++ b/siriuspy/siriuspy/devices/injsys.py @@ -241,8 +241,8 @@ def cmd_turn_off(self): # wait for PS change opmode retval = self._wait_devices_propty( self._psdevs, 'OpMode-Sts', - [_PSConst.States.SlowRef, _PSConst.States.Off], - comp='contains', timeout=3, return_prob=True) + len(self._psdevs)*[[_PSConst.States.SlowRef, _PSConst.States.Off]], + comp=lambda x, y: x in y, timeout=3, return_prob=True) if not retval[0]: text = 'Check for BO PS to be in OpMode SlowRef '\ 'timed out without success! Verify BO PS!' diff --git a/siriuspy/siriuspy/devices/intlkctrl.py b/siriuspy/siriuspy/devices/intlkctrl.py index 0397bfde1..81babf0f1 100644 --- a/siriuspy/siriuspy/devices/intlkctrl.py +++ b/siriuspy/siriuspy/devices/intlkctrl.py @@ -79,6 +79,8 @@ class BLInterlockCtrl(_Device): TIMEOUT_SHUTTER = 7 # [s] TIMEOUT_EPS_RESET = 3 # [s] + _DEF_MSG_SUCCESS = 'Ok' + class DEVICES: """Devices names.""" @@ -149,86 +151,111 @@ def __init__(self, devname=None, props2init='all', **kwargs): devname = self.DEVICES.CAX if devname not in self.DEVICES.ALL: raise NotImplementedError(devname) + self._error_log = self._DEF_MSG_SUCCESS super().__init__(devname, props2init=props2init, **kwargs) + @property + def error_log(self): + """Return error log for last check method invoked.""" + return self._error_log + @property def is_hutchA_intlk_search_done(self): """.""" - return self['A:PPS01:SEARCH_OK'] == 1 + msg = 'hutch A interlock search not Ok.' + return self._check_set_error_log(self['A:PPS01:SEARCH_OK'] == 1, msg) @property def is_hutchB_intlk_search_done(self): """.""" - return self['B:PPS01:SEARCH_OK'] == 1 + msg = 'hutch B interlock search not Ok.' + return self._check_set_error_log(self['B:PPS01:SEARCH_OK'] == 1, msg) @property def is_machine_gamma_enabled(self): """.""" - return self['M:PPS01:HABILITACAO_MAQUINA'] == 1 + msg = 'machine gamma not enabled.' + return self._check_set_error_log( + self['M:PPS01:HABILITACAO_MAQUINA'] == 1, msg) @property def is_frontend_gamma_shutter_opened(self): """.""" - return self['F:PPS01:GS_X_STATUS'] == 0 + msg = 'front-end gamma shutter not opened.' + return self._check_set_error_log(self['F:PPS01:GS_X_STATUS'] == 0, msg) @property def is_frontend_photon_shutter_opened(self): """.""" - return self['F:PPS01:PS_STATUS'] == 0 + msg = 'front-end photon shutter not opened.' + return self._check_set_error_log(self['F:PPS01:PS_STATUS'] == 0, msg) @property def is_hutchA_gamma_shutter_opened(self): """.""" - return self['A:PPS01:PG_STATUS'] == 0 + msg = 'hutch A gamma shutter not opened.' + return self._check_set_error_log(self['A:PPS01:PG_STATUS'] == 0, msg) @property def is_hutchA_eps_dvf_pos_ok(self): """.""" - return bool(self['A:EPS01:StatusPos']) + msg = 'hutch A EPS DVF position not Ok.' + return self._check_set_error_log(bool(self['A:EPS01:StatusPos']), msg) @property def is_hutchA_eps_temperatures_ok(self): """.""" - return bool(self['A:EPS01:StatusTemp']) + msg = 'hutch A EPS temperatures not Ok.' + return self._check_set_error_log(bool(self['A:EPS01:StatusTemp']), msg) @property def is_hutchA_eps_vacuum_ok(self): """.""" - return bool(self['A:EPS01:StatusVac']) + msg = 'hutch A EPS vacuum not Ok.' + return self._check_set_error_log(bool(self['A:EPS01:StatusVac']), msg) @property def is_hutchB_eps_vacuum_ok(self): """.""" - return bool(self['B:EPS01:StatusVac']) + msg = 'hutch B EPS vacuum not Ok.' + return self._check_set_error_log(bool(self['B:EPS01:StatusVac']), msg) @property def is_frontend_eps_mirror_pos_ok(self): """.""" - return bool(self['F:EPS01:StatusPos']) + msg = 'front-end EPS mirror position not Ok.' + return self._check_set_error_log(bool(self['F:EPS01:StatusPos']), msg) @property def is_frontend_eps_temperatures_ok(self): """.""" - return bool(self['F:EPS01:StatusTemp']) + msg = 'front-end EPS temperatures not Ok.' + return self._check_set_error_log(bool(self['F:EPS01:StatusTemp']), msg) @property def is_frontend_eps_vacuum_ok(self): """.""" - return bool(self['F:EPS01:StatusVac']) + msg = 'front-end EPS vacuum not Ok.' + return self._check_set_error_log(bool(self['F:EPS01:StatusVac']), msg) @property def is_frontend_gatevalves_opened(self): """.""" - if not bool(self['A:EPS01:GV5open']): + msg = 'front-end gatevalve A:EPS01:GV5 not opened.' + if not self._check_set_error_log(bool(self['A:EPS01:GV5open']), msg): return False - if not bool(self['F:EPS01:GV4open']): + msg = 'front-end gatevalve F:EPS01:GV4 not opened.' + if not self._check_set_error_log(bool(self['F:EPS01:GV4open']), msg): return False - if not bool(self['F:EPS01:GV3open']): + msg = 'front-end gatevalve F:EPS01:GV3 not opened.' + if not self._check_set_error_log(bool(self['F:EPS01:GV3open']), msg): return False # NOTE: GV2open not installed yet. - # if not bool(self['F:EPS01:GV2open']): + # msg = 'front-end gatevalve F:EPS01:GV2 not opened.' + # if not self._check_set_error_log(bool(self['F:EPS01:GV2open']), msg): # return False - if not bool(self['F:EPS01:GV1open']): + msg = 'front-end gatevalve F:EPS01:GV1 not opened.' + if not self._check_set_error_log(bool(self['F:EPS01:GV1open']), msg): return False return True @@ -250,9 +277,11 @@ def is_frontend_gatevalves_closed(self): @property def is_hutchB_gatevalves_opened(self): """.""" - if not bool(self['B:EPS01:GV7open']): + msg = 'hutch B gatevalve B:EPS01:GV7 not opened.' + if not self._check_set_error_log(bool(self['B:EPS01:GV7open']), msg): return False - if not bool(self['A:EPS01:GV6open']): + msg = 'hutch B gatevalve B:EPS01:GV6 not opened.' + if not self._check_set_error_log(bool(self['A:EPS01:GV6open']), msg): return False return True @@ -268,68 +297,65 @@ def is_hutchB_gatevalves_closed(self): @property def is_frontend_shutter_eps_permission_ok(self): """.""" - return bool(self['F:PPS01:HABILITACAO_EPS_GS_X']) + msg = 'front-end shutter EPS permission not Ok.' + return self._check_set_error_log( + bool(self['F:PPS01:HABILITACAO_EPS_GS_X']), msg) @property def is_hutchA_shutter_eps_permission_ok(self): """.""" - return bool(self['A:PPS01:HABILITACAO_EPS']) + msg = 'hutch A EPS permission not Ok.' + return self._check_set_error_log( + bool(self['A:PPS01:HABILITACAO_EPS']), msg) @property def is_frontend_eps_ok(self): """.""" - state = True - state &= self.is_frontend_eps_mirror_pos_ok - state &= self.is_frontend_eps_temperatures_ok - state &= self.is_frontend_eps_vacuum_ok - return state + if self.is_frontend_eps_mirror_pos_ok and \ + self.is_frontend_eps_mirror_pos_ok and \ + self.is_frontend_eps_temperatures_ok and \ + self.is_frontend_eps_vacuum_ok: + return True + else: + return False @property def is_hutchA_eps_ok(self): """.""" - state = True - state &= self.is_hutchA_eps_dvf_pos_ok - state &= self.is_hutchA_eps_temperatures_ok - state &= self.is_hutchA_eps_vacuum_ok - return state + if self.is_hutchA_eps_dvf_pos_ok and \ + self.is_hutchA_eps_temperatures_ok and \ + self.is_hutchA_eps_vacuum_ok: + return True + else: + return False @property def is_hutchB_eps_ok(self): """.""" - state = True - state &= self.is_hutchB_eps_vacuum_ok - return state + return self.is_hutchB_eps_vacuum_ok @property def is_beamline_eps_ok(self): """.""" - state = True - state &= self.is_frontend_eps_ok - state &= self.is_hutchA_eps_ok - state &= self.is_hutchB_eps_ok - return state + if self.is_frontend_eps_ok and \ + self.is_hutchA_eps_ok and \ + self.is_hutchB_eps_ok: + return True + else: + return False @property def is_beamline_opened(self): """Return whether BL is opened.""" - if not self.is_hutchA_intlk_search_done: - return False - if not self.is_hutchB_intlk_search_done: - return False - if not self.is_machine_gamma_enabled: - return False - if not self.is_beamline_eps_ok: - return False - if not self.is_frontend_shutter_eps_permission_ok: - return False - if not self.is_hutchA_shutter_eps_permission_ok: - return False - if not self.is_frontend_gamma_shutter_opened or \ - not self.is_frontend_photon_shutter_opened: - return False - if not self.is_hutchA_gamma_shutter_opened: - return False - return True + return self.is_hutchA_intlk_search_done and \ + self.is_hutchB_intlk_search_done and \ + self.is_machine_gamma_enabled and \ + self.is_beamline_eps_ok and \ + self.is_frontend_shutter_eps_permission_ok and \ + self.is_hutchA_shutter_eps_permission_ok and \ + self.is_frontend_gamma_shutter_opened and \ + self.is_frontend_photon_shutter_opened and \ + self.is_hutchA_gamma_shutter_opened def cmd_beamline_eps_reset(self): """.""" @@ -347,7 +373,6 @@ def cmd_frontend_gatevalves_open(self, timeout=None): t0 = _time.time() while not self.is_frontend_gatevalves_opened: if _time.time() - t0 > timeout: - print('open frontend gatevalve timeout reached!') return False _time.sleep(0.5) @@ -365,7 +390,7 @@ def cmd_hutchB_gatevalves_open(self, timeout=None): t0 = _time.time() while not self.is_hutchB_gatevalves_opened: if _time.time() - t0 > timeout: - print('open hutchB gatevalve timeout reached!') + print(self.error_log) return False _time.sleep(0.5) @@ -386,7 +411,7 @@ def cmd_frontend_gamma_and_photon_open(self, timeout=None): not self.is_frontend_gamma_shutter_opened or \ not self.is_frontend_photon_shutter_opened: if _time.time() - t0 > timeout: - print('open frontend shutter timeout reached!') + print(self.error_log) return False _time.sleep(0.5) @@ -426,7 +451,7 @@ def cmd_hutchA_photon_open(self, timeout=None): t0 = _time.time() while not self.is_hutchA_gamma_shutter_opened: if _time.time() - t0 > timeout: - print('open hutchA photon shutter timeout reached!') + print(self.error_log) return False _time.sleep(0.5) @@ -453,47 +478,44 @@ def cmd_hutchA_photon_close(self, timeout=None): def cmd_beamline_open(self): """.""" if not self.is_hutchA_intlk_search_done: - print('hutchA search is not done!') + print(self.error_log) return False if not self.is_hutchB_intlk_search_done: - print('hutchB search is not done!') + print(self.error_log) return False if not self.is_machine_gamma_enabled: - print('machine gamma signal not enabled.') + print(self.error_log) return False # check and reset EPS if not self.is_beamline_eps_ok: - print('beamline eps reset') self.cmd_beamline_eps_reset() t0 = _time.time() while not self.is_beamline_eps_ok: if _time.time() - t0 > self.TIMEOUT_EPS_RESET: - print('eps reset timeout reached!') + print(self.error_log) return False _time.sleep(0.5) # check frontend shutter permission and open gatevalves for hutchA if not self.is_frontend_shutter_eps_permission_ok: - print('open frontend and hutchA gatevalves') # open frontend and hutchA gatevalves self.cmd_frontend_gatevalves_open() t0 = _time.time() while not self.is_frontend_gatevalves_opened: if _time.time() - t0 > self.TIMEOUT_GATEVALVE: - msg = 'open frontend and hutchA gatevalve timeout reached!' - print(msg) + print(self.error_log) return False _time.sleep(0.5) # check hutchA shutter permission and open gatevalves for hutchB if not self.is_hutchA_shutter_eps_permission_ok: - print('open hutchB gatevalves') is_ok = self.cmd_hutchB_gatevalves_open( timeout=self.TIMEOUT_GATEVALVE) if not is_ok: + print(self.error_log) return False # open frontend gamma and photon shutter @@ -501,12 +523,28 @@ def cmd_beamline_open(self): is_ok = self.cmd_frontend_gamma_and_photon_open( timeout=self.TIMEOUT_SHUTTER) if not is_ok: + print(self.error_log) return False # open hutchA photon shutter print('open hutchA photon shutter') is_ok = self.cmd_hutchA_photon_open(timeout=self.TIMEOUT_SHUTTER) if not is_ok: + print(self.error_log) return False return True + + def _set_error_log(self, msg): + # NOTE: should we prefix the msg with timestamp? + self._error_log = msg + + def _check_set_error_log(self, cond, msg_fail, msg_succeed=None): + msg_succeed = BLInterlockCtrl._DEF_MSG_SUCCESS if msg_succeed is None \ + else msg_succeed + if not cond: + self._set_error_log(msg_fail) + return False + else: + self._set_error_log(msg_succeed) + return True diff --git a/siriuspy/siriuspy/devices/orbit_interlock.py b/siriuspy/siriuspy/devices/orbit_interlock.py index 41fcecbe0..a4806e071 100644 --- a/siriuspy/siriuspy/devices/orbit_interlock.py +++ b/siriuspy/siriuspy/devices/orbit_interlock.py @@ -115,7 +115,7 @@ def calc_intlk_metric(self, posarray, operation='', metric=''): func = self._oper[operation] val = func(dval, uval) data_values.append(val) - return data_values + return _np.array(data_values) @staticmethod def _mean(var1, var2): @@ -127,100 +127,98 @@ def _diff(var1, var2): class BPMOrbitIntlk(BaseOrbitIntlk, _Device): - """This device group the orbit interlock PVs from one BPM.""" + """This device group the orbit interlock PVs from one BPM.""" PROPERTIES_DEFAULT = ( - # ============================================================== - # Basic properties - 'PosX-Mon', 'PosY-Mon', 'Sum-Mon', # ============================================================== # General - # +++++++ + # ************************************************************** # General interlock enable: 'IntlkEn-Sel', 'IntlkEn-Sts', - # General interlock clear: - 'IntlkClr-Cmd', # maybe -Cmd? + # General interlock reset: + 'IntlkClr-Cmd', # Minimum sum threshold enable: - # Habilita interlock de órbita apenas quando threshold da soma - # ultrapassar o valor em "IntlkLmtMinSum-SP" + # Enable orbit interlock only when sum is higher than IntlkLmtMinSum-RB 'IntlkMinSumEn-Sel', 'IntlkMinSumEn-Sts', - # Minimum sum threshold (em contagens da Soma da taxa FAcq): + # Minimum sum threshold (sum counts in FAcq rate): 'IntlkLmtMinSum-SP', 'IntlkLmtMinSum-RB', - # Status Instantâneo: - # Interlock instântaneo, dificilmente será detectado com - # a implementação atual do gateware + # Instantaneous interlock, difficult to be checked in the current + # gateware implementation 'Intlk-Mon', - # Latch do interlock, limpo apenas acionando-se a PV "Clr" - # correspondente + # Latch interlock, clean only when respective "Clr" PV is triggered 'IntlkLtc-Mon', # =============================================================== - # Position (interlock de posição) - # +++++++++++++++++++++++++++++++++++++ + # Position Interlock # *************************************************************** - # Condição para interlock de posição: + # Condition for position interlock: # thres_min > (pos BPM downstream + pos BPM upstream)/2 or # thres_max < (pos BPM downstream + pos BPM upstream)/2 - # BPMs são agrupados 2 a 2 seguindo a ordem do feixe: + # BPMs are grouped 2 by 2 following the order seen by the beam: # - M1/M2 # - C1-1/C1-2 # - C2/C3-1 # - C3-2/C4 - # BPM upstream é sempre o "primeiro" BPM da dupla acima e BPM - # downstream é sempre o "segundo" BPM da dupla. + # upstream BPM is always the "first" BPM of the pairs above and + # downstream BPM is always the "second" BPM of the pair. # *************************************************************** # Position interlock enable: 'IntlkPosEn-Sel', 'IntlkPosEn-Sts', # Position interlock clear: 'IntlkPosClr-Cmd', - # Thresholds (em nm da taxa Monit1): + # Thresholds (nm, FAcq rate): 'IntlkLmtPosMaxX-SP', 'IntlkLmtPosMaxX-RB', 'IntlkLmtPosMinX-SP', 'IntlkLmtPosMinX-RB', 'IntlkLmtPosMaxY-SP', 'IntlkLmtPosMaxY-RB', 'IntlkLmtPosMinY-SP', 'IntlkLmtPosMinY-RB', - # Todos os interlocks são mascarados pelo "Enable" - # Status Instantâneo: + # All interlocks are masked by the "Enable" state + # Instantaneous Status: 'IntlkPosLower-Mon', 'IntlkPosUpper-Mon', # X ou Y 'IntlkPosLowerX-Mon', 'IntlkPosUpperX-Mon', # X 'IntlkPosLowerY-Mon', 'IntlkPosUpperY-Mon', # Y - # Status Latch, limpo apenas acionando-se a PV "Clr" correspondente: + # Latch Status, clean only when respective "Clr" PV is triggered 'IntlkPosLowerLtc-Mon', 'IntlkPosUpperLtc-Mon', 'IntlkPosLowerLtcX-Mon', 'IntlkPosUpperLtcX-Mon', 'IntlkPosLowerLtcY-Mon', 'IntlkPosUpperLtcY-Mon', + # Position measure from orbit interlock core + 'IntlkPosX-Mon', + 'IntlkPosY-Mon', # ============================================================= - # Angular (interlock de ângulo) - # +++++++++++++++++++++++++++++ + # Angle Interlock # ************************************************************* - # Condição para interlock de ângulo: - # thres_min > (posição BPM downstream - posição BPM upstream) or - # thres_max < (posição BPM downstream - posição BPM upstream) - # BPMs são agrupados 2 a 2 seguindo a ordem do feixe: + # Condition for angle interlock: + # thres_min > (pos BPM downstream - pos BPM upstream) or + # thres_max < (pos BPM downstream - pos BPM upstream) + # BPMs are grouped 2 by 2 following the order seen by the beam: # - M1/M2 # - C1-1/C1-2 # - C2/C3-1 # - C3-2/C4 - # BPM upstream é sempre o "primeiro" BPM da dupla acima e BPM - # downstream é sempre o "segundo" BPM da dupla. + # upstream BPM is always the "first" BPM of the pairs above and + # downstream BPM is always the "second" BPM of the pair. # ************************************************************ - # Angulation interlock enable: + # Angle interlock enable: 'IntlkAngEn-Sel', 'IntlkAngEn-Sts', - # Angulation interlock clear: + # Angle interlock clear: 'IntlkAngClr-Cmd', - # Thresholds (em rad.nm da taxa FAcq). - # Thresholds devem ser calculados como ângulo (em rad) - # entre os 2 BPMs adjacentes * distância (em nm) entre eles): + # Thresholds (rad.nm, FAcq rate). + # Thresholds must be calculated as angle (rad) + # between the 2 adjacent BPMs * distance (nm) between them: 'IntlkLmtAngMaxX-SP', 'IntlkLmtAngMaxX-RB', 'IntlkLmtAngMinX-SP', 'IntlkLmtAngMinX-RB', 'IntlkLmtAngMaxY-SP', 'IntlkLmtAngMaxY-RB', 'IntlkLmtAngMinY-SP', 'IntlkLmtAngMinY-RB', - # Todos os interlocks são mascarados pelo "Enable" - # Status Instantâneo: + # All interlocks are masked by the "Enable" state + # Intantaneous Status 'IntlkAngLower-Mon', 'IntlkAngUpper-Mon', 'IntlkAngLowerX-Mon', 'IntlkAngUpperX-Mon', # X 'IntlkAngLowerY-Mon', 'IntlkAngUpperY-Mon', # Y - # Status Latch, limpo apenas acionando-se a PV "Clr" correspondente: + # Latch Status, clean only when respective "Clr" PV is triggered 'IntlkAngLowerLtc-Mon', 'IntlkAngUpperLtc-Mon', 'IntlkAngLowerLtcX-Mon', 'IntlkAngUpperLtcX-Mon', 'IntlkAngLowerLtcY-Mon', 'IntlkAngUpperLtcY-Mon', + # Angle measure from orbit interlock core + 'IntlkAngX-Mon', + 'IntlkAngY-Mon', # ============================================================ ) @@ -233,19 +231,24 @@ def __init__(self, devname, props2init='all'): _Device.__init__(self, devname, props2init=props2init) @property - def posx(self): - """Position X, Monit rate.""" - return self['PosX-Mon'] * self.CONV_NM2UM + def intlkposx(self): + """Orbit interlock core Position X.""" + return self['IntlkPosX-Mon'] * self.CONV_NM2UM + + @property + def intlkposy(self): + """Orbit interlock core Position Y.""" + return self['IntlkPosY-Mon'] * self.CONV_NM2UM @property - def posy(self): - """Position Y, Monit rate.""" - return self['PosY-Mon'] * self.CONV_NM2UM + def intlkangx(self): + """Orbit interlock core Angle X.""" + return self['IntlkAngX-Mon'] * self.CONV_NM2UM @property - def possum(self): - """Sum, Monit rate.""" - return self['Sum-Mon'] + def intlkangy(self): + """Orbit interlock core Angle Y.""" + return self['IntlkAngY-Mon'] * self.CONV_NM2UM @property def pair_down_up_bpms(self): @@ -281,26 +284,26 @@ def gen_latch(self): # --- minimum sum threshold --- @property - def minsumthres_enable(self): + def minsum_enable(self): """ Minimum sum threshold enable. - If enabled, generate orbit interlock only when sum threshold exceeds - value in 'minsumthres' property. + If enabled, generate orbit interlock only when sum exceeds + value in 'minsum_thres' property. """ return self['IntlkMinSumEn-Sts'] - @minsumthres_enable.setter - def minsumthres_enable(self, value): + @minsum_enable.setter + def minsum_enable(self, value): self['IntlkMinSumEn-Sel'] = int(value) @property - def minsumthres(self): + def minsum_thres(self): """Minimum sum threshold [sum count, FAcq rate].""" return self['IntlkLmtMinSum-RB'] - @minsumthres.setter - def minsumthres(self, value): + @minsum_thres.setter + def minsum_thres(self, value): self['IntlkLmtMinSum-SP'] = int(value) # --- position interlock --- @@ -320,39 +323,39 @@ def cmd_reset_pos(self): return True @property - def pos_thresminx(self): + def pos_x_min_thres(self): """Minimum X position threshold.""" return self['IntlkLmtPosMinX-RB'] - @pos_thresminx.setter - def pos_thresminx(self, value): + @pos_x_min_thres.setter + def pos_x_min_thres(self, value): self['IntlkLmtPosMinX-SP'] = value @property - def pos_thresmaxx(self): + def pos_x_max_thres(self): """Maximum X position threshold.""" return self['IntlkLmtPosMaxX-RB'] - @pos_thresmaxx.setter - def pos_thresmaxx(self, value): + @pos_x_max_thres.setter + def pos_x_max_thres(self, value): self['IntlkLmtPosMaxX-SP'] = value @property - def pos_thresminy(self): + def pos_y_min_thres(self): """Minimum Y position threshold.""" return self['IntlkLmtPosMinY-RB'] - @pos_thresminy.setter - def pos_thresminy(self, value): + @pos_y_min_thres.setter + def pos_y_min_thres(self, value): self['IntlkLmtPosMinY-SP'] = value @property - def pos_thresmaxy(self): + def pos_y_max_thres(self): """Maximum Y position threshold.""" return self['IntlkLmtPosMaxY-RB'] - @pos_thresmaxy.setter - def pos_thresmaxy(self, value): + @pos_y_max_thres.setter + def pos_y_max_thres(self, value): self['IntlkLmtPosMaxY-SP'] = value @property @@ -468,39 +471,39 @@ def cmd_reset_ang(self): return True @property - def ang_thresminx(self): + def ang_x_min_thres(self): """Minimum X angulation threshold.""" return self['IntlkLmtAngMinX-RB'] - @ang_thresminx.setter - def ang_thresminx(self, value): + @ang_x_min_thres.setter + def ang_x_min_thres(self, value): self['IntlkLmtAngMinX-SP'] = value @property - def ang_thresmaxx(self): + def ang_x_max_thres(self): """Maximum X angulation threshold.""" return self['IntlkLmtAngMaxX-RB'] - @ang_thresmaxx.setter - def ang_thresmaxx(self, value): + @ang_x_max_thres.setter + def ang_x_max_thres(self, value): self['IntlkLmtAngMaxX-SP'] = value @property - def ang_thresminy(self): + def ang_y_min_thres(self): """Minimum Y angulation threshold.""" return self['IntlkLmtAngMinY-RB'] - @ang_thresminy.setter - def ang_thresminy(self, value): + @ang_y_min_thres.setter + def ang_y_min_thres(self, value): self['IntlkLmtAngMinY-SP'] = value @property - def ang_thresmaxy(self): + def ang_y_max_thres(self): """Maximum Y angulation threshold.""" return self['IntlkLmtAngMaxY-RB'] - @ang_thresmaxy.setter - def ang_thresmaxy(self, value): + @ang_y_max_thres.setter + def ang_y_max_thres(self, value): self['IntlkLmtAngMaxY-SP'] = value @property @@ -624,19 +627,38 @@ def __init__(self, devname=None, props2init='all'): # --- general interlock --- - def cmd_gen_enable(self, timeout=TIMEOUT): + @property + def gen_enable(self): + """General interlock enable. + + Returns: + enbl (numpy.ndarray, 160): + enable state for each BPM. + """ + return _np.array([b.gen_enable for b in self._devices]) + + def set_gen_enable(self, value, timeout=TIMEOUT, return_prob=False): + """Set enable state for BPM general interlock.""" + self._set_devices_propty(self.devices, 'IntlkEn-Sel', value) + return self._wait_devices_propty( + self.devices, 'IntlkEn-Sts', value, timeout=timeout, + return_prob=return_prob) + + def cmd_gen_enable(self, timeout=TIMEOUT, return_prob=False): """Enable all BPM general interlock.""" for dev in self.devices: dev.gen_enable = 1 return self._wait_devices_propty( - self.devices, 'IntlkEn-Sts', 1, timeout=timeout) + self.devices, 'IntlkEn-Sts', 1, timeout=timeout, + return_prob=return_prob) - def cmd_gen_disable(self, timeout=TIMEOUT): + def cmd_gen_disable(self, timeout=TIMEOUT, return_prob=False): """Disable all BPM general interlock.""" for dev in self.devices: dev.gen_enable = 0 return self._wait_devices_propty( - self.devices, 'IntlkEn-Sts', 0, timeout=timeout) + self.devices, 'IntlkEn-Sts', 0, timeout=timeout, + return_prob=return_prob) def cmd_reset_gen(self): """Reset all BPM general interlock.""" @@ -666,50 +688,99 @@ def gen_latch(self): # --- minimum sum threshold --- - def cmd_minsumthres_enable(self, timeout=TIMEOUT): + @property + def minsum_enable(self): + """Minimum sum threshold enable. + + Returns: + enbl (numpy.ndarray, 160): + enable state for each BPM. + """ + return _np.array([b.minsum_enable for b in self._devices]) + + def set_minsum_enable(self, value, timeout=TIMEOUT, return_prob=False): + """Set enable state for BPM minimum sum interlock.""" + self._set_devices_propty(self.devices, 'IntlkMinSumEn-Sel', value) + return self._wait_devices_propty( + self.devices, 'IntlkMinSumEn-Sts', value, timeout=timeout, + return_prob=return_prob) + + def cmd_minsum_enable(self, timeout=TIMEOUT, return_prob=False): """Enable all BPM minimum sum threshold.""" for dev in self.devices: - dev.minsumthres_enable = 1 + dev.minsum_enable = 1 return self._wait_devices_propty( - self.devices, 'IntlkMinSumEn-Sts', 1, timeout=timeout) + self.devices, 'IntlkMinSumEn-Sts', 1, timeout=timeout, + return_prob=return_prob) - def cmd_minsumthres_disable(self, timeout=TIMEOUT): + def cmd_minsum_disable(self, timeout=TIMEOUT, return_prob=False): """Disable all BPM minimum sum threshold.""" for dev in self.devices: - dev.minsumthres_enable = 0 + dev.minsum_enable = 0 return self._wait_devices_propty( - self.devices, 'IntlkMinSumEn-Sts', 0, timeout=timeout) + self.devices, 'IntlkMinSumEn-Sts', 0, timeout=timeout, + return_prob=return_prob) @property - def minsumthres(self): + def minsum_thres(self): """Minimum sum thresholds. Returns: thres (numpy.ndarray, 160): min.sum threshold for each BPM. """ - return _np.array([b.minsumthres for b in self._devices]) + return _np.array([b.minsum_thres for b in self._devices]) + + def set_minsum_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set minimum sum thresholds. - @minsumthres.setter - def minsumthres(self, value): + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. + + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.minsumthres = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtMinSum-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtMinSum-RB', value, timeout=timeout, + return_prob=return_prob) # --- position interlock --- - def cmd_pos_enable(self, timeout=TIMEOUT): + @property + def pos_enable(self): + """Position interlock enable. + + Returns: + enbl (numpy.ndarray, 160): + enable state for each BPM. + """ + return _np.array([b.pos_enable for b in self._devices]) + + def set_pos_enable(self, value, timeout=TIMEOUT, return_prob=False): + """Set enable state for BPM position interlock.""" + self._set_devices_propty(self.devices, 'IntlkPosEn-Sel', value) + return self._wait_devices_propty( + self.devices, 'IntlkPosEn-Sts', value, timeout=timeout, + return_prob=return_prob) + + def cmd_pos_enable(self, timeout=TIMEOUT, return_prob=False): """Enable all BPM position interlock.""" for dev in self.devices: dev.pos_enable = 1 return self._wait_devices_propty( - self.devices, 'IntlkPosEn-Sts', 1, timeout=timeout) + self.devices, 'IntlkPosEn-Sts', 1, timeout=timeout, + return_prob=return_prob) - def cmd_pos_disable(self, timeout=TIMEOUT): + def cmd_pos_disable(self, timeout=TIMEOUT, return_prob=False): """Disable all BPM position interlock.""" for dev in self.devices: dev.pos_enable = 0 return self._wait_devices_propty( - self.devices, 'IntlkPosEn-Sts', 0, timeout=timeout) + self.devices, 'IntlkPosEn-Sts', 0, timeout=timeout, + return_prob=return_prob) def cmd_reset_pos(self): """Reset all BPM position interlock.""" @@ -718,68 +789,112 @@ def cmd_reset_pos(self): return True @property - def pos_thresminx(self): + def pos_x_min_thres(self): """Minimum x position thresholds. Returns: thres (numpy.ndarray, 160): min. x position threshold for each BPM. """ - return _np.array([b.pos_thresminx for b in self._devices]) + return _np.array([b.pos_x_min_thres for b in self._devices]) - @pos_thresminx.setter - def pos_thresminx(self, value): + def set_pos_x_min_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set minimum x position thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. + + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.pos_thresminx = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtPosMinX-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtPosMinX-RB', value, timeout=timeout, + return_prob=return_prob) @property - def pos_thresmaxx(self): + def pos_x_max_thres(self): """Maximum x position thresholds. Returns: thres (numpy.ndarray, 160): max. x position threshold for each BPM. """ - return _np.array([b.pos_thresmaxx for b in self._devices]) + return _np.array([b.pos_x_max_thres for b in self._devices]) - @pos_thresmaxx.setter - def pos_thresmaxx(self, value): + def set_pos_x_max_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set maximum x position thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. + + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.pos_thresmaxx = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtPosMaxX-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtPosMaxX-RB', value, timeout=timeout, + return_prob=return_prob) @property - def pos_thresminy(self): + def pos_y_min_thres(self): """Minimum y position thresholds. Returns: thres (numpy.ndarray, 160): min. y position threshold for each BPM. """ - return _np.array([b.pos_thresminy for b in self._devices]) + return _np.array([b.pos_y_min_thres for b in self._devices]) - @pos_thresminy.setter - def pos_thresminy(self, value): + def set_pos_y_min_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set minimum y position thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. + + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.pos_thresminy = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtPosMinY-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtPosMinY-RB', value, timeout=timeout, + return_prob=return_prob) @property - def pos_thresmaxy(self): + def pos_y_max_thres(self): """Maximum y position thresholds. Returns: thres (numpy.ndarray, 160): max. y position threshold for each BPM. """ - return _np.array([b.pos_thresmaxy for b in self._devices]) + return _np.array([b.pos_y_max_thres for b in self._devices]) - @pos_thresmaxy.setter - def pos_thresmaxy(self, value): + def set_pos_y_max_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set maximum y position thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. + + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.pos_thresmaxy = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtPosMaxY-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtPosMaxY-RB', value, timeout=timeout, + return_prob=return_prob) @property def pos_inst_lower(self): @@ -903,19 +1018,38 @@ def pos_latch_upper_y(self): # --- angulation interlock --- - def cmd_ang_enable(self, timeout=TIMEOUT): + @property + def ang_enable(self): + """Angle interlock enable. + + Returns: + enbl (numpy.ndarray, 160): + enable state for each BPM. + """ + return _np.array([b.ang_enable for b in self._devices]) + + def set_ang_enable(self, value, timeout=TIMEOUT, return_prob=False): + """Set enable state for BPM angulation interlock.""" + self._set_devices_propty(self.devices, 'IntlkAngEn-Sel', value) + return self._wait_devices_propty( + self.devices, 'IntlkAngEn-Sts', value, timeout=timeout, + return_prob=return_prob) + + def cmd_ang_enable(self, timeout=TIMEOUT, return_prob=False): """Enable all BPM angulation interlock.""" for dev in self.devices: dev.ang_enable = 1 return self._wait_devices_propty( - self.devices, 'IntlkAngEn-Sts', 1, timeout=timeout) + self.devices, 'IntlkAngEn-Sts', 1, timeout=timeout, + return_prob=return_prob) - def cmd_ang_disable(self, timeout=TIMEOUT): + def cmd_ang_disable(self, timeout=TIMEOUT, return_prob=False): """Disable all BPM angulation interlock.""" for dev in self.devices: dev.ang_enable = 0 return self._wait_devices_propty( - self.devices, 'IntlkAngEn-Sts', 0, timeout=timeout) + self.devices, 'IntlkAngEn-Sts', 0, timeout=timeout, + return_prob=return_prob) def cmd_reset_ang(self): """Reset all BPM angulation interlock.""" @@ -924,68 +1058,112 @@ def cmd_reset_ang(self): return True @property - def ang_thresminx(self): + def ang_x_min_thres(self): """Minimum x angulation thresholds. Returns: thres (numpy.ndarray, 160): min. x angulation threshold for each BPM. """ - return _np.array([b.ang_thresminx for b in self._devices]) + return _np.array([b.ang_x_min_thres for b in self._devices]) + + def set_ang_x_min_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set minimum x angle thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. - @ang_thresminx.setter - def ang_thresminx(self, value): + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.ang_thresminx = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtAngMinX-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtAngMinX-RB', value, timeout=timeout, + return_prob=return_prob) @property - def ang_thresmaxx(self): + def ang_x_max_thres(self): """Maximum x angulation thresholds. Returns: thres (numpy.ndarray, 160): max. x angulation threshold for each BPM. """ - return _np.array([b.ang_thresmaxx for b in self._devices]) + return _np.array([b.ang_x_max_thres for b in self._devices]) + + def set_ang_x_max_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set maximum x angle thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. - @ang_thresmaxx.setter - def ang_thresmaxx(self, value): + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.ang_thresmaxx = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtAngMaxX-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtAngMaxX-RB', value, timeout=timeout, + return_prob=return_prob) @property - def ang_thresminy(self): + def ang_y_min_thres(self): """Minimum y angulation thresholds. Returns: thres (numpy.ndarray, 160): min. y angulation threshold for each BPM. """ - return _np.array([b.ang_thresminy for b in self._devices]) + return _np.array([b.ang_y_min_thres for b in self._devices]) + + def set_ang_y_min_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set minimum y angle thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. - @ang_thresminy.setter - def ang_thresminy(self, value): + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.ang_thresminy = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtAngMinY-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtAngMinY-RB', value, timeout=timeout, + return_prob=return_prob) @property - def ang_thresmaxy(self): + def ang_y_max_thres(self): """Maximum y angulation thresholds. Returns: thres (numpy.ndarray, 160): max. y angulation threshold for each BPM. """ - return _np.array([b.ang_thresmaxy for b in self._devices]) + return _np.array([b.ang_y_max_thres for b in self._devices]) + + def set_ang_y_max_thres(self, value, timeout=TIMEOUT, return_prob=False): + """Set maximum y angle thresholds. + + Args: + value (numpy.ndarray|float): Values for minimum sum. + timeout (float, optional): timeout to wait. Defaults to TIMEOUT. - @ang_thresmaxy.setter - def ang_thresmaxy(self, value): + Returns: + bool: True if set was successful. + + """ value = self._handle_thres_input(value) - for idx, dev in enumerate(self.devices): - dev.ang_thresmaxy = value[idx] + self._set_devices_propty(self.devices, 'IntlkLmtAngMaxY-SP', value) + return self._wait_devices_propty( + self.devices, 'IntlkLmtAngMaxY-RB', value, timeout=timeout, + return_prob=return_prob) @property def ang_inst_lower(self): @@ -1107,32 +1285,6 @@ def ang_latch_upper_y(self): """ return _np.array([b.ang_latch_upper_y for b in self._devices]) - @property - def slow_orbit(self): - """Slow orbit vectors. - - Returns: - orbx (numpy.ndarray, 160): Horizontal Orbit. - orby (numpy.ndarray, 160): Vertical Orbit. - - """ - orbx, orby = [], [] - for bpm in self._devices: - orbx.append(bpm.posx) - orby.append(bpm.posy) - orbx = _np.array(orbx) - orby = _np.array(orby) - return orbx, orby - - @property - def possum(self): - """Sum vector, at Monit rate. - - Returns: - possum (numpy.ndarray, 160): Sum vector, at Monit rate. - """ - return _np.array([b.possum for b in self._devices]) - @property def position(self): """Position vectors. @@ -1144,25 +1296,23 @@ def position(self): posx (numpy.ndarray, 160): Horizontal Position. posy (numpy.ndarray, 160): Vertical Position. """ - orbx, orby = self.slow_orbit - posx = _np.array(self.calc_intlk_metric(orbx, metric='pos')) - posy = _np.array(self.calc_intlk_metric(orby, metric='pos')) + posx = _np.array([b.intlkposx for b in self._devices]) + posy = _np.array([b.intlkposy for b in self._devices]) return posx, posy @property - def angulation(self): - """Angulation vectors. + def angle(self): + """Angle vectors. - Angulation at each BPM is defined as: + Angle at each BPM is defined as: (posição BPM downstream - posição BPM upstream) Returns: - angx (numpy.ndarray, 160): Horizontal Angulation. - angy (numpy.ndarray, 160): Vertical Angulation. + angx (numpy.ndarray, 160): Horizontal Angle. + angy (numpy.ndarray, 160): Vertical Angle. """ - orbx, orby = self.slow_orbit - angx = _np.array(self.calc_intlk_metric(orbx, metric='ang')) - angy = _np.array(self.calc_intlk_metric(orby, metric='ang')) + angx = _np.array([b.intlkangx for b in self._devices]) + angy = _np.array([b.intlkangy for b in self._devices]) return angx, angy def _handle_thres_input(self, value): diff --git a/siriuspy/siriuspy/devices/psconv.py b/siriuspy/siriuspy/devices/psconv.py index be49e0b73..d182a4cf7 100644 --- a/siriuspy/siriuspy/devices/psconv.py +++ b/siriuspy/siriuspy/devices/psconv.py @@ -38,12 +38,12 @@ def property_sync(self): @property def value(self): """Return property value.""" - return self.value_get(self.property_sync) + return self.get_value(self.property_sync) @value.setter def value(self, current): """Set property value.""" - self.value_set(self.property_sync, current) + self.set_value(self.property_sync, current) @property def limits(self): diff --git a/siriuspy/siriuspy/devices/pwrsupply.py b/siriuspy/siriuspy/devices/pwrsupply.py index de856bfd7..9fab6b677 100644 --- a/siriuspy/siriuspy/devices/pwrsupply.py +++ b/siriuspy/siriuspy/devices/pwrsupply.py @@ -10,6 +10,7 @@ MAX_WFMSIZE_FBP as _MAX_WFMSIZE_FBP, \ MAX_WFMSIZE_OTHERS as _MAX_WFMSIZE_OTHERS from ..pwrsupply.psctrl.pscstatus import PSCStatus as _PSCStatus +from ..magnet.factory import NormalizerFactory as _NormFactory from .device import Device as _Device from .timing import Trigger as _Trigger @@ -106,6 +107,16 @@ def __init__(self, devname, auto_monitor_mon=False, props2init='all'): # private attribute with strength setpoint pv object self._strength_sp_pv = self.pv_object(self._strength_sp_propty) + try: + name = devname.substitute(dis='MA') + if name.dev == 'B1B2' or (name.sec == 'BO' and name.dev == 'B'): + maname = name.substitute(idx='') + else: + maname = name + self._normalizer = _NormFactory.create(maname) + except: + self._normalizer = None + @property def pstype(self): """Return type of magnet(s) excited by power supply device.""" @@ -141,6 +152,11 @@ def is_magps(self): """Return True if device is a Sirius magnet power supply.""" return self._is_pulsed + @property + def normalizer(self): + """Return Normalizer object for current and strength conversions.""" + return self._normalizer + @property def strength_property(self): """Return Strength name.""" diff --git a/siriuspy/siriuspy/devices/rf.py b/siriuspy/siriuspy/devices/rf.py index 6fed30d10..75c9963d9 100644 --- a/siriuspy/siriuspy/devices/rf.py +++ b/siriuspy/siriuspy/devices/rf.py @@ -1264,55 +1264,35 @@ class SILLRFPreAmp(_Device): class DEVICES: """Devices names.""" - SIA01 = 'RA-RaSIA01:RF-LLRFPreAmp-1' - ALL = (SIA01, ) + SSA1 = 'RA-ToSIA03:RF-CtrlPanel' + SSA2 = 'RA-ToSIA04:RF-CtrlPanel' + ALL = (SSA1, SSA2) PROPERTIES_DEFAULT = ( - 'PINSw1Enbl-Cmd', 'PINSw1Dsbl-Cmd', 'PINSw1-Mon', - 'PINSw2Enbl-Cmd', 'PINSw2Dsbl-Cmd', 'PINSw2-Mon', + 'PINSwEnbl-Cmd', 'PINSwDsbl-Cmd', 'PINSwSts-Mon', ) - def __init__(self, devname='', props2init='all'): - if not devname: - devname = SILLRFPreAmp.DEVICES.SIA01 + def __init__(self, devname, props2init='all'): if devname not in SILLRFPreAmp.DEVICES.ALL: raise NotImplementedError(devname) super().__init__(devname, props2init=props2init) - def cmd_enable_pinsw_1(self, timeout=None, wait_mon=True): + def cmd_enable_pinsw(self, timeout=None, wait_mon=True): """Enable PINSw 1.""" - self['PINSw1Enbl-Cmd'] = 1 - _time.sleep(1) - self['PINSw1Enbl-Cmd'] = 0 - if wait_mon: - return self._wait('PINSw1-Mon', 1, timeout=timeout) - return True - - def cmd_enable_pinsw_2(self, timeout=None, wait_mon=True): - """Enable PINSw 2.""" - self['PINSw2Enbl-Cmd'] = 1 + self['PINSwEnbl-Cmd'] = 1 _time.sleep(1) - self['PINSw2Enbl-Cmd'] = 0 + self['PINSwEnbl-Cmd'] = 0 if wait_mon: - return self._wait('PINSw2-Mon', 1, timeout=timeout) + return self._wait('PINSwSts-Mon', 1, timeout=timeout) return True - def cmd_disable_pinsw_1(self, timeout=None, wait_mon=True): + def cmd_disable_pinsw(self, timeout=None, wait_mon=True): """Disable PINSw 1.""" - self['PINSw1Dsbl-Cmd'] = 1 - _time.sleep(1) - self['PINSw1Dsbl-Cmd'] = 0 - if wait_mon: - return self._wait('PINSw1-Mon', 0, timeout=timeout) - return True - - def cmd_disable_pinsw_2(self, timeout=None, wait_mon=True): - """Disable PINSw 2.""" - self['PINSw2Dsbl-Cmd'] = 1 + self['PINSwDsbl-Cmd'] = 1 _time.sleep(1) - self['PINSw2Dsbl-Cmd'] = 0 + self['PINSwDsbl-Cmd'] = 0 if wait_mon: - return self._wait('PINSw2-Mon', 0, timeout=timeout) + return self._wait('PINSwSts-Mon', 0, timeout=timeout) return True @@ -1361,12 +1341,12 @@ class SIRFDCAmp(_Device): class DEVICES: """Devices names.""" - SSA1 = 'RA-ToSIA01:RF-TDKSource' - SSA2 = 'RA-ToSIA02:RF-TDKSource' + SSA1 = 'RA-ToSIA03:RF-TDKSource' + SSA2 = 'RA-ToSIA04:RF-TDKSource' ALL = (SSA1, SSA2) PROPERTIES_DEFAULT = ( - 'PwrDCEnbl-Sel', 'PwrDCDsbl-Sel', 'PwrDC-Sts', + 'PwrDCEnbl-Cmd', 'PwrDCDsbl-Cmd', 'PwrDC-Mon', ) def __init__(self, devname, props2init='all'): @@ -1376,20 +1356,20 @@ def __init__(self, devname, props2init='all'): def cmd_enable(self, timeout=None, wait_mon=True): """Enable.""" - self['PwrDCEnbl-Sel'] = 1 + self['PwrDCEnbl-Cmd'] = 1 _time.sleep(1) - self['PwrDCEnbl-Sel'] = 0 + self['PwrDCEnbl-Cmd'] = 0 if wait_mon: - return self._wait('PwrDC-Sts', 1, timeout=timeout) + return self._wait('PwrDC-Mon', 1, timeout=timeout) return True def cmd_disable(self, timeout=None, wait_mon=True): """Disable.""" - self['PwrDCDsbl-Sel'] = 1 + self['PwrDCDsbl-Cmd'] = 1 _time.sleep(1) - self['PwrDCDsbl-Sel'] = 0 + self['PwrDCDsbl-Cmd'] = 0 if wait_mon: - return self._wait('PwrDC-Sts', 0, timeout=timeout) + return self._wait('PwrDC-Mon', 0, timeout=timeout) return True @@ -1438,12 +1418,12 @@ class SIRFACAmp(_Device): class DEVICES: """Devices names.""" - SSA1 = 'RA-ToSIA01:RF-ACPanel' - SSA2 = 'RA-ToSIA02:RF-ACPanel' + SSA1 = 'RA-ToSIA03:RF-ACPanel' + SSA2 = 'RA-ToSIA04:RF-ACPanel' ALL = (SSA1, SSA2) PROPERTIES_DEFAULT = ( - 'PwrACEnbl-Sel', 'PwrACDsbl-Sel', 'PwrAC-Sts', + 'PwrACEnbl-Cmd', 'PwrACDsbl-Cmd', 'PwrAC-Mon', ) def __init__(self, devname, props2init='all'): @@ -1453,20 +1433,20 @@ def __init__(self, devname, props2init='all'): def cmd_enable(self, timeout=None, wait_mon=True): """Enable.""" - self['PwrACEnbl-Sel'] = 1 + self['PwrACEnbl-Cmd'] = 1 _time.sleep(1) - self['PwrACEnbl-Sel'] = 0 + self['PwrACEnbl-Cmd'] = 0 if wait_mon: - return self._wait('PwrAC-Sts', 1, timeout=timeout) + return self._wait('PwrAC-Mon', 1, timeout=timeout) return True def cmd_disable(self, timeout=None, wait_mon=True): """Disable.""" - self['PwrACDsbl-Sel'] = 1 + self['PwrACDsbl-Cmd'] = 1 _time.sleep(1) - self['PwrACDsbl-Sel'] = 0 + self['PwrACDsbl-Cmd'] = 0 if wait_mon: - return self._wait('PwrAC-Sts', 0, timeout=timeout) + return self._wait('PwrAC-Mon', 0, timeout=timeout) return True diff --git a/siriuspy/siriuspy/devices/syncd.py b/siriuspy/siriuspy/devices/syncd.py index c1c280e06..2af03779e 100644 --- a/siriuspy/siriuspy/devices/syncd.py +++ b/siriuspy/siriuspy/devices/syncd.py @@ -47,7 +47,7 @@ def synchronized(self): return False return True - def value_get(self, propty): + def get_value(self, propty): """Return property value.""" if not self.connected: return @@ -61,7 +61,7 @@ def value_get(self, propty): return return sum(values) / len(values) - def value_set(self, propty, value): + def set_value(self, propty, value): """Set property.""" if not self.connected: return diff --git a/siriuspy/siriuspy/devices/timing.py b/siriuspy/siriuspy/devices/timing.py index 7a9358ad6..a209df452 100644 --- a/siriuspy/siriuspy/devices/timing.py +++ b/siriuspy/siriuspy/devices/timing.py @@ -271,7 +271,7 @@ class Trigger(_Device): 'StatusLabels-Cte', 'TotalDelay-Mon', 'TotalDelayRaw-Mon', 'WidthRaw-RB', 'WidthRaw-SP') - def __init__(self, trigname, props2init='all'): + def __init__(self, trigname, props2init='all', auto_monitor_mon=False): """Init.""" _database = _get_hl_trigger_database(trigname) all_props = tuple(_database) @@ -282,7 +282,9 @@ def __init__(self, trigname, props2init='all'): else: props2init = list(set(all_props) & set(props2init)) self._source_options = _database['Src-Sel']['enums'] - super().__init__(trigname, props2init=props2init) + super().__init__( + trigname, props2init=props2init, + auto_monitor_mon=auto_monitor_mon) @property def status(self): diff --git a/siriuspy/siriuspy/dvfimgproc/csdev.py b/siriuspy/siriuspy/dvfimgproc/csdev.py index d38ac05a0..dd6a593a7 100644 --- a/siriuspy/siriuspy/dvfimgproc/csdev.py +++ b/siriuspy/siriuspy/dvfimgproc/csdev.py @@ -26,6 +26,9 @@ class Constants(_csdev.Const): NoYes = _get_namedtuple('NoYes', _et.NO_YES) StsLblsDVF = _get_namedtuple('StsLblsDVF', _et.STS_LBLS_DVF) + DEF_DVFSTATUS = 0b11111111 + DEF_BEAM_THRESHOLD = 100 + def __init__(self, devname): """.""" self._devname = devname @@ -114,19 +117,23 @@ def _get_image_db(self): 'value': self.NoYes.No, }, 'ImgIsWithBeamThreshold-SP': { - 'type': 'int', 'unit': 'intensity', 'value': 10, + 'type': 'int', 'unit': 'intensity', + 'value': self.DEF_BEAM_THRESHOLD, }, 'ImgIsWithBeamThreshold-RB': { - 'type': 'int', 'unit': 'intensity', 'value': 10, + 'type': 'int', 'unit': 'intensity', + 'value': self.DEF_BEAM_THRESHOLD, }, } return dbase def _get_roi_db(self): + dvf_params = _DVF.conv_devname2parameters(self.devname) db = {} rb_ = '-RB' sp_ = '-SP' mon_ = '-Mon' + sizes = {'X': dvf_params.IMAGE_SIZE_X, 'Y': dvf_params.IMAGE_SIZE_Y} for axis in ['X', 'Y']: db.update({ 'ImgROI' + axis + sp_: { @@ -141,6 +148,9 @@ def _get_roi_db(self): 'ImgROI' + axis + 'FWHM' + mon_: { 'type': 'int', 'unit': 'px' }, + 'ImgROI' + axis + 'Proj' + mon_: { + 'type': 'int', 'count': sizes[axis], 'unit': 'intensity', + }, 'ImgROI' + axis + 'UpdateWithFWHMFactor-SP': { 'type': 'float', 'value': 2.0, 'unit': 'fwhm_factor', 'prec': 3, 'lolim': 0, 'hilim': 2000, @@ -227,7 +237,7 @@ def _get_others_db(self): 'type': 'int', 'value': 0, }, 'ImgDVFStatus-Mon': { - 'type': 'int', 'value': 0b11111111, + 'type': 'int', 'value': self.DEF_DVFSTATUS, }, 'ImgDVFStatusLabels-Cte': { 'type': 'string', 'count': len(self.StsLblsDVF._fields), diff --git a/siriuspy/siriuspy/dvfimgproc/main.py b/siriuspy/siriuspy/dvfimgproc/main.py index 42f92700a..fc29e7176 100644 --- a/siriuspy/siriuspy/dvfimgproc/main.py +++ b/siriuspy/siriuspy/dvfimgproc/main.py @@ -34,6 +34,7 @@ class App: 'ImgROIX-RB': ('fitx', 'roi'), 'ImgROIXCenter-Mon': ('fitx', 'roi_center'), 'ImgROIXFWHM-Mon': ('fitx', 'roi_fwhm'), + 'ImgROIXProj-Mon': ('fitx', 'roi_proj'), # --- roix_fit --- 'ImgROIXFitAmplitude-Mon': ('fitx', 'roi_amplitude'), 'ImgROIXFitMean-Mon': ('fitx', 'roi_mean'), @@ -43,6 +44,7 @@ class App: 'ImgROIY-RB': ('fity', 'roi'), 'ImgROIYCenter-Mon': ('fity', 'roi_center'), 'ImgROIYFWHM-Mon': ('fity', 'roi_fwhm'), + 'ImgROIYProj-Mon': ('fity', 'roi_proj'), # --- roiy_fit --- 'ImgROIYFitAmplitude-Mon': ('fity', 'roi_amplitude'), 'ImgROIYFitMean-Mon': ('fity', 'roi_mean'), @@ -200,15 +202,20 @@ def update_driver(self): if not self.meas.image2dfit.is_with_image: self._write_pv('ImgIsWithBeam-Mon', 0) - return invalid_fitx, invalid_fity = [False]*2 + with_beam = self.meas.image2dfit.is_with_image + for pvname, attr in App._MON_PVS_2_IMGFIT.items(): # check if is roi_rb and if it needs updating if pvname in ('ImgROIX-RB', 'ImgROIY-RB'): if not self.meas.update_roi_with_fwhm: continue + # if no beam, return if PV is of fit type + if not with_beam and 'Fit' in pvname and 'ProcTime' not in pvname: + return + # get image attribute value value = self._conv_imgattr2value(attr) if value is None: @@ -287,7 +294,7 @@ def _create_meas(self): # create object meas = MeasDVF( - self.const.devname, + self.const, fwhmx_factor=fwhmx_factor, fwhmy_factor=fwhmy_factor, roi_with_fwhm=roi_with_fwhm, intensity_threshold=intensity_threshold, diff --git a/siriuspy/siriuspy/dvfimgproc/meas.py b/siriuspy/siriuspy/dvfimgproc/meas.py index e35f964d3..3023549a0 100644 --- a/siriuspy/siriuspy/dvfimgproc/meas.py +++ b/siriuspy/siriuspy/dvfimgproc/meas.py @@ -17,10 +17,11 @@ class MeasDVF(): MIN_ROI_SIZE = 5 # [pixels] def __init__( - self, devname, fwhmx_factor, fwhmy_factor, roi_with_fwhm, + self, const, fwhmx_factor, fwhmy_factor, roi_with_fwhm, intensity_threshold, use_svd4theta, callback=None): """.""" - self._devname = devname + self._const = const + self._devname = const.devname self._callback = callback self._status = MeasDVF.STATUS_SUCCESS self._dvf = None @@ -58,7 +59,7 @@ def dvf(self): @property def status_dvf(self): """.""" - status = 0b00000000 + status = 0 * self._const.DEF_DVFSTATUS status |= ((1 if not self.dvf.connected else 0) << 0) status |= ((1 if not self.dvf.acquisition_status else 0) << 1) return status diff --git a/siriuspy/siriuspy/envars.py b/siriuspy/siriuspy/envars.py index 914837f64..863622dd2 100644 --- a/siriuspy/siriuspy/envars.py +++ b/siriuspy/siriuspy/envars.py @@ -43,7 +43,7 @@ 'SIRIUS_URL_CABLES', default='http://cables:8086') SRVURL_CSCONSTS = _os.environ.get( 'SIRIUS_URL_CONSTS', - default='http://10.128.255.4/control-system-constants') + default='http://10.0.38.59/control-system-constants') SRVURL_CSCONSTS_2 = _os.environ.get( 'SIRIUS_URL_CONSTS_2', default='http://10.128.255.3/control-system-constants') @@ -52,7 +52,7 @@ default='http://10.0.38.42/Olog') SRVURL_CONFIGDB = _os.environ.get( 'SIRIUS_URL_CONFIGDB', - default='http://10.128.255.4/config-db') + default='http://10.0.38.59/config-db') SRVURL_CONFIGDB_2 = _os.environ.get( 'SIRIUS_URL_CONFIGDB_2', default='http://10.128.255.3/config-db') diff --git a/siriuspy/siriuspy/idff/config.py b/siriuspy/siriuspy/idff/config.py index 307fe0647..47edacf26 100644 --- a/siriuspy/siriuspy/idff/config.py +++ b/siriuspy/siriuspy/idff/config.py @@ -11,19 +11,28 @@ class IDFFConfig(_ConfigDBDocument): """Insertion Device Feedforward Configuration.""" - # NOTE: for EPU50 there is a large discrepancy - # between RB/SP/Mon phase values - PPARAM_TOL = 0.5 # [mm] - KPARAM_TOL = 0.1 # [mm] CONFIGDB_TYPE = 'si_idff' def __init__(self, name=None, url=None): """.""" name_ = name or 'idff_' + self.generate_config_name() + self._idname = None self._polarization_definitions = None super().__init__( config_type=IDFFConfig.CONFIGDB_TYPE, name=name_, url=url) + @property + def idname(self): + """Return idname corresponding to IDFFConfig.""" + return self._idname + + @idname.setter + def idname(self, value): + """Set idname.""" + if value not in _IDSearch.get_idnames(): + raise ValueError(f'{value} is not a valid idname!') + self._idname = value + @property def pparameter_pvname(self): """Return ID pparameter pvname.""" @@ -77,22 +86,27 @@ def value(self, value): """Set configuration.""" self._set_value(value) - def calculate_setpoints(self, polarization, kparameter_value): + def calculate_setpoints( + self, polarization, pparameter_value, kparameter_value): """Return correctors setpoints for a particular ID config. - The parameter 'kparameter' can be a gap or phase value, - depending on the insertion device. + 'pparameter' is the ID phase which defines polarization. + 'kparameter' can be a gap or phase value, depending on the ID. """ if self._value: setpoints = dict() idff = self._value['polarizations'][polarization] - kparameter_values = idff['kparameter'] + if polarization == 'none': + params = idff['pparameter'] + param_value = pparameter_value + else: + params = idff['kparameter'] + param_value = kparameter_value setpoints = dict() for corrlabel, table in idff.items(): if corrlabel not in ('pparameter', 'kparameter'): # linear interpolation - setpoint = _np.interp( - kparameter_value, kparameter_values, table) + setpoint = _np.interp(param_value, params, table) corr_pvname = self._value['pvnames'][corrlabel] setpoints[corr_pvname] = setpoint return setpoints @@ -121,15 +135,18 @@ def create_template_config(idname): ptable['pparameter'] = [0, 0] ptable['kparameter'] = 0 ktable = dict(ctable) - ktable['pparameter'] = 0 ktable['kparameter'] = [0, 0] + pol2pparam = _IDSearch.conv_idname_2_polarization_pparameter polarizations = dict() - for polarization in idff['polarizations']: - if polarization == 'none': - polarizations[polarization] = dict(ptable) + for pol in idff['polarizations']: + if pol == _IDSearch.POL_UNDEF_STR: + continue + if pol == _IDSearch.POL_NONE_STR: + polarizations[pol] = dict(ptable) else: - polarizations[polarization] = dict(ktable) + ktable['pparameter'] = pol2pparam(idname, pol) + polarizations[pol] = dict(ktable) template_config = dict( description=description, pvnames=pvnames, @@ -143,6 +160,7 @@ def __str__(self): value = self.value if value is None: return stg + stg += f'\ndescription: {value["description"]}' stg += '\n--- pvnames ---' pvnames = ''.join( @@ -164,36 +182,81 @@ def __str__(self): def load(self, discarded=False): """.""" super().load(discarded=discarded) + self._find_idname() self._calc_polariz_defs() def get_polarization_state(self, pparameter, kparameter): """Return polarization state based on ID parameteres.""" - poldefs = self._polarization_definitions - if poldefs is None: - raise ValueError('No IDFF configuration defined.') - for pol, val in poldefs.items(): - if pol == 'none': - continue - if val is None or abs(pparameter - val) < IDFFConfig.PPARAM_TOL: - return pol - if abs(kparameter - poldefs['none']) < IDFFConfig.KPARAM_TOL: - return 'none' - return 'not_defined' + idname = self._idname + pol_idx = _IDSearch.conv_idname_2_polarization_state( + idname, pparameter, kparameter) + pol_str = _IDSearch.conv_idname_2_polarizations_sts(idname)[pol_idx] + return pol_str def check_valid_value(self, value): - """.""" - if not super().check_valid_value(value): - return False - for pol, table in value['polarizations'].items(): - if pol == 'none': - nrpts = len(table['pparameter']) - else: - nrpts = len(table['kparameter']) - for key, value_ in table.items(): - if key in ('kparameter', 'pparameter'): - continue - if len(value_) != nrpts: - return False + """Check consistency of SI_IDFF configuration.""" + configs = value['polarizations'] + pvnames = { + key: value for key, value in value['pvnames'].items() + if key not in ('pparameter', 'kparameter')} + corrlabels = set(pvnames.keys()) + + # check pvnames in configs + pvsconfig = set(pvnames.values()) + getch = _IDSearch.conv_idname_2_idff_chnames + getcv = _IDSearch.conv_idname_2_idff_cvnames + getqs = _IDSearch.conv_idname_2_idff_qsnames + chnames = [corr + ':Current-SP' for corr in getch(self.idname)] + cvnames = [corr + ':Current-SP' for corr in getcv(self.idname)] + qsnames = [corr + ':Current-SP' for corr in getqs(self.idname)] + pvsidsearch = set(chnames + cvnames + qsnames) + symm_diff = pvsconfig ^ pvsidsearch + + if symm_diff: + raise ValueError('List of pvnames in config is not consistent') + + # check polarizations in configs + pconfig = set(configs.keys()) - set((_IDSearch.POL_NONE_STR, )) + pidsearch = set(_IDSearch.conv_idname_2_polarizations(self.idname)) + symm_diff = pconfig ^ pidsearch + + if symm_diff: + raise ValueError( + 'List of polarizations in config is not consistent') + + # check polarization tables consistency + for polarization, table in configs.items(): + corrtable = { + key: value for key, value in table.items() + if key not in ('pparameter', 'kparameter')} + + # check 'pparameter' + if 'pparameter' not in table: + raise ValueError( + 'Missing pparameter in polarization configuration.') + + # check 'kparameter' + if 'kparameter' not in table: + raise ValueError( + 'Missing kparameter in polarization configuration.') + + # check corr label list + corrlabels_config = set(corrtable.keys()) + symm_diff = corrlabels ^ corrlabels_config + if symm_diff: + raise ValueError( + 'List of corrlabels in config is not consistent') + + # check nrpts in tables + param = 'pparameter' if polarization == 'none' else 'kparameter' + nrpts_corrtables = {len(table) for table in corrtable.values()} + nrpts_kparameter = set([len(table[param]), ]) + symm_diff = nrpts_corrtables ^ nrpts_kparameter + + if symm_diff: + raise ValueError( + 'Corrector tables and kparameter list in config ' + 'are not consistent') return True def _get_corr_pvnames(self, cname1, cname2): @@ -207,15 +270,40 @@ def _get_corr_pvnames(self, cname1, cname2): def _set_value(self, value): super()._set_value(value) + self._find_idname() self._calc_polariz_defs() def _calc_polariz_defs(self): """.""" + # fill polarization data struct data = self._value['polarizations'] poldefs = dict() for pol, tab in data.items(): - if pol != 'none': + if pol != _IDSearch.POL_NONE_STR: poldefs[pol] = tab['pparameter'] else: poldefs[pol] = tab['kparameter'] self._polarization_definitions = poldefs + + def _find_idname(self): + """.""" + # find associated idname + self._idname = None + pvnames = self._value['pvnames'] + kparameter, pparameter = pvnames['kparameter'], pvnames['pparameter'] + for idname in _IDSearch.get_idnames(): + kparam_propty = _IDSearch.conv_idname_2_kparameter_propty(idname) + pparam_propty = _IDSearch.conv_idname_2_pparameter_propty(idname) + if None in (kparam_propty, pparam_propty): + continue + kparam = idname + ':' + kparam_propty + pparam = idname + ':' + pparam_propty + + if kparam == kparameter and pparam == pparameter: + self._idname = idname + break + if self._idname is None: + # could not find idname + raise ValueError( + 'kparameter and pparameter in config are not ' + 'associated with an idname!') diff --git a/siriuspy/siriuspy/idff/csdev.py b/siriuspy/siriuspy/idff/csdev.py index 26f93ae17..e65de0f90 100644 --- a/siriuspy/siriuspy/idff/csdev.py +++ b/siriuspy/siriuspy/idff/csdev.py @@ -14,8 +14,7 @@ class ETypes(_csdev.ETypes): OPEN_CLOSED = ('Open', 'Closed') STS_LBLS_CORR = ( - 'Connected', 'PwrStateOn', 'OpModeConfigured', - 'SOFBModeConfigured') + 'Connected', 'PwrStateOn', 'OpModeConfigured') _et = ETypes @@ -30,7 +29,13 @@ class IDFFConst(_csdev.Const): StsLblsCorr = _csdev.Const.register( 'StsLblsCorr', _et.STS_LBLS_CORR) - DEFAULT_LOOP_FREQ = 5 # [Hz] + DEFAULT_CORR_STATUS = 0b11111111 + DEFAULT_LOOP_FREQ_MIN = 0.001 # [Hz] + DEFAULT_LOOP_FREQ_MAX = 100 # [Hz] + DEFAULT_LOOP_FREQ = 10 # [Hz] + DEFAULT_LOOP_STATE = LoopState.Open + DEFAULT_CONTROL_QS = _csdev.Const.DsblEnbl.Enbl + DEFAULT_CORR_PREC = 4 def __init__(self, idname): """Init.""" @@ -51,41 +56,58 @@ def get_propty_database(self): 'Log-Mon': {'type': 'string', 'value': 'Starting...'}, 'LoopState-Sel': { 'type': 'enum', 'enums': _et.OPEN_CLOSED, - 'value': self.LoopState.Open}, + 'value': self.DEFAULT_LOOP_STATE}, 'LoopState-Sts': { 'type': 'enum', 'enums': _et.OPEN_CLOSED, - 'value': self.LoopState.Open}, + 'value': self.DEFAULT_LOOP_STATE}, 'LoopFreq-SP': { 'type': 'float', 'value': self.DEFAULT_LOOP_FREQ, - 'unit': 'Hz', 'prec': 3, 'lolim': 1e-3, 'hilim': 60}, + 'unit': 'Hz', 'prec': 3, + 'lolim': self.DEFAULT_LOOP_FREQ_MIN, + 'hilim': self.DEFAULT_LOOP_FREQ_MAX}, 'LoopFreq-RB': { 'type': 'float', 'value': self.DEFAULT_LOOP_FREQ, - 'unit': 'Hz', 'prec': 3, 'lolim': 1e-3, 'hilim': 60}, + 'unit': 'Hz', 'prec': 3, + 'lolim': self.DEFAULT_LOOP_FREQ_MIN, + 'hilim': self.DEFAULT_LOOP_FREQ_MAX}, 'Polarization-Mon': {'type': 'string', 'value': 'none'}, 'ConfigName-SP': {'type': 'string', 'value': ''}, 'ConfigName-RB': {'type': 'string', 'value': ''}, - 'SOFBMode-Sel': { - 'type': 'enum', 'enums': _et.DSBL_ENBL, - 'value': self.DsblEnbl.Dsbl, 'unit': 'sofbmode'}, - 'SOFBMode-Sts': { - 'type': 'enum', 'enums': _et.DSBL_ENBL, - 'value': self.DsblEnbl.Dsbl, 'unit': 'sofbmode'}, 'CorrConfig-Cmd': {'type': 'int', 'value': 0}, - 'CorrStatus-Mon': {'type': 'int', 'value': 0b1111}, + 'CorrStatus-Mon': { + 'type': 'int', 'value': self.DEFAULT_CORR_STATUS}, 'CorrStatusLabels-Cte': { 'type': 'string', 'count': len(self.StsLblsCorr._fields), - 'value': self.StsLblsCorr._fields} + 'value': self.StsLblsCorr._fields}, + 'CorrCH1Current-Mon': { + 'type': 'float', 'value': 0, + 'unit': 'A', 'prec': self.DEFAULT_CORR_PREC}, + 'CorrCH2Current-Mon': { + 'type': 'float', 'value': 0, + 'unit': 'A', 'prec': self.DEFAULT_CORR_PREC}, + 'CorrCV1Current-Mon': { + 'type': 'float', 'value': 0, + 'unit': 'A', 'prec': self.DEFAULT_CORR_PREC}, + 'CorrCV2Current-Mon': { + 'type': 'float', 'value': 0, + 'unit': 'A', 'prec': self.DEFAULT_CORR_PREC}, } if self.has_qscorrs: dbase.update({ 'ControlQS-Sel': { 'type': 'enum', 'enums': _et.DSBL_ENBL, - 'value': self.DsblEnbl.Enbl, + 'value': self.DEFAULT_CONTROL_QS, 'unit': 'If QS are included in loop'}, 'ControlQS-Sts': { 'type': 'enum', 'enums': _et.DSBL_ENBL, - 'value': self.DsblEnbl.Enbl, + 'value': self.DEFAULT_CONTROL_QS, 'unit': 'If QS are included in loop'}, + 'CorrQS1Current-Mon': { + 'type': 'float', 'value': 0, + 'unit': 'A', 'prec': self.DEFAULT_CORR_PREC}, + 'CorrQS2Current-Mon': { + 'type': 'float', 'value': 0, + 'unit': 'A', 'prec': self.DEFAULT_CORR_PREC}, }) dbase = _csdev.add_pvslist_cte(dbase) return dbase diff --git a/siriuspy/siriuspy/idff/main.py b/siriuspy/siriuspy/idff/main.py index d1bfd9c48..85f8af76e 100644 --- a/siriuspy/siriuspy/idff/main.py +++ b/siriuspy/siriuspy/idff/main.py @@ -3,20 +3,14 @@ import os as _os import logging as _log import time as _time -from importlib.util import find_spec as _find_spec import epics as _epics import numpy as _np -if _find_spec('PRUserial485') is not None: - from PRUserial485 import EthBridgeClient as _EthBridgeClient - from ..util import update_bit as _updt_bit from ..callbacks import Callback as _Callback from ..clientconfigdb import ConfigDBException as _ConfigDBException from ..devices import IDFF as _IDFF -from ..pwrsupply.pssofb import PSConnSOFB as _PSConnSOFB -from ..pwrsupply.pssofb import PSNamesSOFB as _PSNamesSOFB from .csdev import IDFFConst as _Const, ETypes as _ETypes @@ -33,18 +27,16 @@ def __init__(self, idname): self._pvs_prefix = self._const.idffname self._pvs_database = self._const.get_propty_database() - self._loop_state = _Const.DsblEnbl.Dsbl + self._loop_state = _Const.DEFAULT_LOOP_STATE self._loop_freq = _Const.DEFAULT_LOOP_FREQ - self._control_qs = _Const.DsblEnbl.Dsbl + self._control_qs = _Const.DEFAULT_CONTROL_QS self._polarization = 'none' self._config_name = '' self.read_autosave_file() + # IDFF object with IDFF config self._idff = _IDFF(idname) - self._pssofb_isused = self._pvs_database['SOFBMode-Sts']['value'] - self._pssofb, self._bsmp_devs = self._pssofb_init(idname) - # load idff in configdb self._load_config(self._config_name) @@ -54,13 +46,13 @@ def __init__(self, idname): 'LoopFreq-SP': self.set_loop_freq, 'ControlQS-Sel': self.set_control_qs, 'ConfigName-SP': self.set_config_name, - 'SOFBMode-Sel': self.set_sofb_mode, 'CorrConfig-Cmd': self.cmd_corrconfig, } self._quit = False + self._corr_setpoints = None self._thread_ff = _epics.ca.CAThread( - target=self._do_ff, daemon=True) + target=self.main_idff_loop, daemon=True) self._thread_ff.start() def init_database(self): @@ -73,10 +65,8 @@ def init_database(self): 'ConfigName-SP': self._config_name, 'ConfigName-RB': self._config_name, 'Polarization-Mon': self._polarization, - 'SOFBMode-Sel': self._pssofb_isused, - 'SOFBMode-Sts': self._pssofb_isused, 'CorrConfig-Cmd': 0, - 'CorrStatus-Mon': 0b1111, + 'CorrStatus-Mon': _Const.DEFAULT_CORR_STATUS, } if self._const.has_qscorrs: pvn2vals.update({ @@ -102,6 +92,7 @@ def process(self, interval): # check correctors state periodically _t0 = _time.time() self._update_corr_status() + self._update_corr_setpoints() dtime = _time.time() - _t0 sleep_time = interval - dtime # sleep @@ -138,10 +129,11 @@ def set_loop_state(self, value): def set_loop_freq(self, value): """Set loop frequency.""" - if not 1e-3 <= value < 60: + fmin, fmax = _Const.DEFAULT_LOOP_FREQ_MIN, _Const.DEFAULT_LOOP_FREQ_MAX + if not fmin <= value < fmax: return False self._loop_freq = value - self._update_log(f'Loop frequency updated to {value:.2f}Hz.') + self._update_log(f'Loop frequency updated to {value:.3f}Hz.') self.run_callbacks('LoopFreq-RB', value) return True @@ -169,36 +161,11 @@ def set_config_name(self, value): self.run_callbacks('ConfigName-RB', value) return True - def set_sofb_mode(self, value): - """Set whether to use SOFBMode.""" - if not 0 <= value < len(_ETypes.DSBL_ENBL): - return False - - if self._loop_state == self._const.LoopState.Closed: - self._update_log('ERR:Open loop before changing configuration.') - return False - - if not self._idff_prepare_corrs_state(value): - self._update_log( - ('ERR:Could not configure IDFF correctors ' - 'when changing SOFBMode.')) - return False - - self._pssofb_isused = bool(value) - status = 'enabled' if self._pssofb_isused else 'disabled' - self._update_log(f'SOFBMode {status}.') - self.run_callbacks('SOFBMode-Sts', value) - - return True - def cmd_corrconfig(self, _): """Command to reconfigure power supplies to desired state.""" if self._loop_state == self._const.LoopState.Closed: self._update_log('ERR:Open loop before configure correctors.') return False - if self._pssofb_isused: - self._update_log('ERR:Turn off PSSOFB mode before configure.') - return False corrdevs = self._idff.chdevs + self._idff.cvdevs + self._idff.qsdevs for dev in corrdevs: @@ -211,15 +178,6 @@ def cmd_corrconfig(self, _): return True - def _load_config(self, config_name): - try: - self._idff.load_config(config_name) - self._update_log(f'Updated configuration: {config_name}.') - except (ValueError, _ConfigDBException) as err: - self._update_log('ERR:'+str(err)) - return False - return True - @property def quit(self): """Quit and shutdown threads.""" @@ -229,7 +187,38 @@ def quit(self): def quit(self, value): if value: self._quit = value - self._pssofb.threads_shutdown() + + def main_idff_loop(self): + """Main IDFF loop.""" + while not self._quit: + # updating interval + tplanned = 1.0/self._loop_freq + + # initial time + _t0 = _time.time() + + # check IDFF device connection + if not self._idff.connected: + self._do_sleep(_t0, tplanned) + continue + + # update polarization state + self._do_update_polarization() + + # correctors value calculation + self._do_update_correctors() + + # return if loop is not closed + if not self._loop_state: + self._do_sleep(_t0, tplanned) + continue + + # setpoints implementation + if self._corr_setpoints: + self._do_implement_correctors() + + # sleep unused time or signal overtime to stdout + self._do_sleep(_t0, tplanned) # ----- log auxiliary methods ----- @@ -275,8 +264,19 @@ def _get_default_configname(self): return 'epu50_ref' elif self._const.idname.dev == 'PAPU50': return 'papu50_ref' + elif self._const.idname.dev == 'DELTA52': + return 'delta52_ref' return '' + def _load_config(self, config_name): + try: + self._idff.load_config(config_name) + self._update_log(f'Updated configuration: {config_name}.') + except (ValueError, _ConfigDBException) as err: + self._update_log('ERR:'+str(err)) + return False + return True + # ----- update pvs methods ----- def _do_sleep(self, time0, tplanned): @@ -296,63 +296,30 @@ def _do_update_polarization(self): self.run_callbacks('Polarization-Mon', new_pol) def _do_update_correctors(self): - corrdevs = None - if self._control_qs == self._const.DsblEnbl.Dsbl: - corrdevs = self._idff.chdevs + self._idff.cvdevs try: - # ret = self._idff.calculate_setpoints() - # setpoints, polarization, *parameters = ret + self._corr_setpoints = self._idff.calculate_setpoints() + # setpoints, polarization, *parameters = self._corr_setpoints # pparameter_value, kparameter_value = parameters # print('pparameter: ', pparameter_value) # print('kparameter: ', kparameter_value) # print('polarization: ', polarization) # print('setpoints: ', setpoints) # print() - if self._pssofb_isused: - # calc setpoints - ret = self._idff.calculate_setpoints() - setpoints, *_ = ret - - # built curr_sp vector - curr_sp = self._pssfob_get_current_setpoint( - setpoints, corrdevs) - - # apply curr_sp to pssofb - self._pssofb.bsmp_sofb_current_set_update((curr_sp, )) - else: - # use PS IOCs (IDFF) to implement setpoints - self._idff.implement_setpoints(corrdevs=corrdevs) - except ValueError as err: self._update_log('ERR:'+str(err)) - def _do_ff(self): - # updating loop - while not self._quit: - # updating interval - tplanned = 1.0/self._loop_freq - - # initial time - _t0 = _time.time() - - # check IDFF device connection - if not self._idff.connected: - self._do_sleep(_t0, tplanned) - continue - - # update polarization state - self._do_update_polarization() - - # return if loop is not closed - if not self._loop_state: - self._do_sleep(_t0, tplanned) - continue - - # correctors setpoint implementation - self._do_update_correctors() + def _do_implement_correctors(self): + corrdevs = None + if self._control_qs == self._const.DsblEnbl.Dsbl: + corrdevs = self._idff.chdevs + self._idff.cvdevs + try: + # use PS IOCs (IDFF) to implement setpoints + setpoints, *_ = self._corr_setpoints + self._idff.implement_setpoints( + setpoints=setpoints, corrdevs=corrdevs) - # sleep unused time or signal overtime to stdout - self._do_sleep(_t0, tplanned) + except ValueError as err: + self._update_log('ERR:'+str(err)) def _update_corr_status(self): """Update CorrStatus-Mon PV.""" @@ -366,56 +333,34 @@ def _update_corr_status(self): status = _updt_bit(status, 1, 1) if any(d.opmode != d.OPMODE_STS.SlowRef for d in devs): status = _updt_bit(status, 2, 1) - if any(d.sofbmode != self._pssofb_isused for d in devs): - status = _updt_bit(status, 3, 1) else: - status = 0b111 + status = _Const.DEFAULT_CORR_STATUS self.run_callbacks('CorrStatus-Mon', status) - # ----- idff ----- - - def _idff_prepare_corrs_state(self, pssofb_isused): + def _update_corr_setpoints(self): + """Update corrector setpoint PVs.""" + if self._corr_setpoints is None: + return + setpoints, *_ = self._corr_setpoints + idff = self._idff + corrnames = idff.chnames + idff.cvnames + idff.qsnames + corrlabels = ('CH1', 'CH2', 'CV1', 'CV2', 'QS1', 'QS2') + for corrlabel, corrname in zip(corrlabels, corrnames): + for corr_pvname in setpoints: + if corrname in corr_pvname: + pvname = 'Corr' + corrlabel + 'Current-Mon' + value = setpoints[corr_pvname] + self.run_callbacks(pvname, value) + + # ----- idff preparation ----- + + def _idff_prepare_corrs_state(self): """Configure PSSOFB mode state .""" if not self._idff.wait_for_connection(): return False corrdevs = self._idff.chdevs + self._idff.cvdevs + self._idff.qsdevs for dev in corrdevs: - if pssofb_isused: - if not dev.cmd_sofbmode_enable(timeout=App.DEF_PS_TIMEOUT): - return False - else: - if not dev.cmd_sofbmode_disable(timeout=App.DEF_PS_TIMEOUT): - return False + if not dev.cmd_sofbmode_disable(timeout=App.DEF_PS_TIMEOUT): + return False return True - - # ----- pssofb ----- - - def _pssofb_init(self, idname): - """Create PSSOFB connections to control correctors.""" - # bbbnames - bbbnames = _PSNamesSOFB.get_bbbnames(idname) - - # create pssofb object - pssofb = _PSConnSOFB( - ethbridgeclnt_class=_EthBridgeClient, - bbbnames=bbbnames, - sofb_update_iocs=True, - acc=idname) - - # build bsmpid -> psname dict - bsmp_devs = dict() - for devices in pssofb.bbb2devs.values(): - for devname, bsmpid in devices: - bsmp_devs[bsmpid] = devname - - return pssofb, bsmp_devs - - def _pssfob_get_current_setpoint(self, setpoints, corrdevs): - """Convert IDFF dict setpoints to PSSOFB list setpoints.""" - current_sp = _np.ones(len(setpoints)) * _np.nan - devnames = [dev.devname for dev in corrdevs] - for bsmpid, devname in self._bsmp_devs.items(): - if devname in devnames: - current_sp[bsmpid - 1] = setpoints[devname] - return current_sp diff --git a/siriuspy/siriuspy/injctrl/bias_feedback.py b/siriuspy/siriuspy/injctrl/bias_feedback.py index 69f14b84e..598b6fb27 100644 --- a/siriuspy/siriuspy/injctrl/bias_feedback.py +++ b/siriuspy/siriuspy/injctrl/bias_feedback.py @@ -1,15 +1,15 @@ """.""" import logging as _log -from epics.ca import CAThread as _Thread +import GPy as gpy import numpy as _np +from epics.ca import CAThread as _Thread from numpy.polynomial import polynomial as _np_poly -import GPy as gpy from .csdev import Const as _Const, get_biasfb_database as _get_database -class BiasFeedback(): +class BiasFeedback: """.""" def __init__(self, injctrl): @@ -17,66 +17,68 @@ def __init__(self, injctrl): db_ = _get_database() self.database = db_ self._injctrl = injctrl - self.loop_state = db_['BiasFBLoopState-Sel']['value'] + self.loop_state = db_["BiasFBLoopState-Sel"]["value"] self.already_set = False - self.min_bias_voltage = db_['BiasFBMinVoltage-SP']['value'] # [V] - self.max_bias_voltage = db_['BiasFBMaxVoltage-SP']['value'] # [V] + self.min_bias_voltage = db_["BiasFBMinVoltage-SP"]["value"] # [V] + self.max_bias_voltage = db_["BiasFBMaxVoltage-SP"]["value"] # [V] - self.model_type = db_['BiasFBModelType-Sel']['value'] - self.model_max_num_points = db_['BiasFBModelMaxNrPts-SP']['value'] - self.model_auto_fit_rate = db_[ - 'BiasFBModelAutoFitEveryNrPts-SP']['value'] - self.model_auto_fit = db_['BiasFBModelAutoFitParams-Sel']['value'] - self.model_update_data = db_['BiasFBModelUpdateData-Sel']['value'] + self.model_type = db_["BiasFBModelType-Sel"]["value"] + self.model_max_num_points = db_["BiasFBModelMaxNrPts-SP"]["value"] + self.model_auto_fit_rate = db_["BiasFBModelAutoFitEveryNrPts-SP"][ + "value" + ] + self.model_auto_fit = db_["BiasFBModelAutoFitParams-Sel"]["value"] + self.model_update_data = db_["BiasFBModelUpdateData-Sel"]["value"] - self.linmodel_angcoeff = db_[ - 'BiasFBLinModAngCoeff-SP']['value'] # [V/mA] - self.linmodel_offcoeff = db_[ - 'BiasFBLinModOffCoeff-SP']['value'] # [V] + # [V/mA]: + self.linmodel_angcoeff = db_["BiasFBLinModAngCoeff-SP"]["value"] + self.linmodel_offcoeff = db_["BiasFBLinModOffCoeff-SP"]["value"] # [V] self._npts_after_fit = 0 self.bias_data = _np.array( - db_['BiasFBModelDataBias-Mon']['value'], dtype=float) + db_["BiasFBModelDataBias-Mon"]["value"], dtype=float + ) self.injc_data = _np.array( - db_['BiasFBModelDataInjCurr-Mon']['value'], dtype=float) + db_["BiasFBModelDataInjCurr-Mon"]["value"], dtype=float + ) self.gpmodel = None self._initialize_models() self.do_update_models = False - pv_ = self._injctrl.currinfo_dev.pv_object('InjCurr-Mon') + pv_ = self._injctrl.currinfo_dev.pv_object("InjCurr-Mon") pv_.auto_monitor = True pv_.add_callback(self._callback_to_thread) # pvname to write method map self.map_pv2write = { - 'LoopState-Sel': self.set_loop_state, - 'MinVoltage-SP': self.set_min_voltage, - 'MaxVoltage-SP': self.set_max_voltage, - 'ModelType-Sel': self.set_model_type, - 'ModelMaxNrPts-SP': self.set_max_num_pts, - 'ModelFitParamsNow-Cmd': self.cmd_fit_params, - 'ModelAutoFitParams-Sel': self.set_auto_fit, - 'ModelAutoFitEveryNrPts-SP': self.set_auto_fit_rate, - 'ModelUpdateData-Sel': self.set_update_data, - 'ModelDataBias-SP': self.set_data_bias, - 'ModelDataInjCurr-SP': self.set_data_injcurr, - 'LinModAngCoeff-SP': self.set_lin_ang_coeff, - 'LinModOffCoeff-SP': self.set_lin_off_coeff, - 'GPModNoiseStd-SP': self.set_gp_noise_std, - 'GPModKernStd-SP': self.set_gp_kern_std, - 'GPModKernLenScl-SP': self.set_gp_kern_leng, - 'GPModNoiseStdFit-Sel': self.set_gp_noise_std_fit, - 'GPModKernStdFit-Sel': self.set_gp_kern_std_fit, - 'GPModKernLenSclFit-Sel': self.set_gp_kern_leng_fit, - } + "LoopState-Sel": self.set_loop_state, + "MinVoltage-SP": self.set_min_voltage, + "MaxVoltage-SP": self.set_max_voltage, + "ModelType-Sel": self.set_model_type, + "ModelMaxNrPts-SP": self.set_max_num_pts, + "ModelFitParamsNow-Cmd": self.cmd_fit_params, + "ModelAutoFitParams-Sel": self.set_auto_fit, + "ModelAutoFitEveryNrPts-SP": self.set_auto_fit_rate, + "ModelUpdateData-Sel": self.set_update_data, + "ModelDataBias-SP": self.set_data_bias, + "ModelDataInjCurr-SP": self.set_data_injcurr, + "LinModAngCoeff-SP": self.set_lin_ang_coeff, + "LinModOffCoeff-SP": self.set_lin_off_coeff, + "GPModNoiseStd-SP": self.set_gp_noise_std, + "GPModKernStd-SP": self.set_gp_kern_std, + "GPModKernLenScl-SP": self.set_gp_kern_leng, + "GPModNoiseStdFit-Sel": self.set_gp_noise_std_fit, + "GPModKernStdFit-Sel": self.set_gp_kern_std_fit, + "GPModKernLenSclFit-Sel": self.set_gp_kern_leng_fit, + } def init_database(self): """Set initial PV values.""" for key, pvdbase in self.database.items(): pvname = key.split(_Const.BIASFB_PROPTY_PREFIX)[1] - self.run_callbacks(pvname, pvdbase['value']) + self.run_callbacks(pvname, pvdbase["value"]) @property def use_gpmodel(self): @@ -95,13 +97,14 @@ def linmodel_coeffs_inverse(self): """.""" ang = self.linmodel_angcoeff off = self.linmodel_offcoeff - return (-off/ang, 1/ang) + return (-off / ang, 1 / ang) def get_delta_current_per_pulse( - self, per=1, nrpul=1, curr_avg=100, curr_now=99.5, ltime=17*3600): + self, per=1, nrpul=1, curr_avg=100, curr_now=99.5, ltime=17 * 3600 + ): """.""" ltime = max(_Const.BIASFB_MINIMUM_LIFETIME, ltime) - curr_tar = curr_avg / (1 - per/2/ltime) + curr_tar = curr_avg / (1 - per / 2 / ltime) dcurr = (curr_tar - curr_now) / nrpul return dcurr @@ -118,67 +121,68 @@ def run_callbacks(self, name, *args, **kwd): if self._injctrl.has_callbacks: self._injctrl.run_callbacks(name, *args, **kwd) return - self.database[name]['value'] = args[0] + self.database[name]["value"] = args[0] def update_log(self, msg): """.""" - self._injctrl.run_callbacks('Log-Mon', msg) + self._injctrl.run_callbacks("Log-Mon", msg) # ###################### Setter methods for IOC ###################### def set_loop_state(self, value): """.""" self.loop_state = bool(value) - self.run_callbacks('LoopState-Sts', value) + self.run_callbacks("LoopState-Sts", value) return True def set_min_voltage(self, value): """.""" self.min_bias_voltage = value self._update_models() - self.run_callbacks('MinVoltage-RB', value) + self.run_callbacks("MinVoltage-RB", value) return True def set_max_voltage(self, value): """.""" self.max_bias_voltage = value self._update_models() - self.run_callbacks('MaxVoltage-RB', value) + self.run_callbacks("MaxVoltage-RB", value) return True def set_model_type(self, value): """.""" self.model_type = value - self.run_callbacks('ModelType-Sts', value) + self.run_callbacks("ModelType-Sts", value) return True def set_max_num_pts(self, value): """.""" self.model_max_num_points = int(value) self._update_models() - self.run_callbacks('ModelMaxNrPts-RB', value) + self.run_callbacks("ModelMaxNrPts-RB", value) return True def cmd_fit_params(self, value): """.""" + _ = value self._update_models(force_optimize=True) return True def set_auto_fit(self, value): """.""" self.model_auto_fit = bool(value) - self.run_callbacks('ModelAutoFitParams-Sts', value) + self.run_callbacks("ModelAutoFitParams-Sts", value) return True def set_auto_fit_rate(self, value): """.""" self.model_auto_fit_rate = int(value) - self.run_callbacks('ModelAutoFitEveryNrPts-RB', value) + self.run_callbacks("ModelAutoFitEveryNrPts-RB", value) return True def set_update_data(self, value): """.""" self.model_update_data = bool(value) - self.run_callbacks('ModelUpdateData-Sts', value) + self.run_callbacks("ModelUpdateData-Sts", value) return True def set_data_bias(self, value): @@ -186,13 +190,13 @@ def set_data_bias(self, value): self.bias_data = _np.array(value) max_ = _Const.BIASFB_MAX_DATA_SIZE if self.bias_data.size > max_: - msg = f'WARN: Bias data is too big (>{max_:d}). ' - msg += 'Trimming first points.' + msg = f"WARN: Bias data is too big (>{max_:d}). " + msg += "Trimming first points." _log.warning(msg) self.update_log(msg) self.bias_data = self.bias_data[-max_:] self._update_models() - self.run_callbacks('ModelDataBias-RB', value) + self.run_callbacks("ModelDataBias-RB", value) return True def set_data_injcurr(self, value): @@ -200,48 +204,48 @@ def set_data_injcurr(self, value): self.injc_data = _np.array(value) max_ = _Const.BIASFB_MAX_DATA_SIZE if self.injc_data.size > max_: - msg = f'WARN: InjCurr data is too big (>{max_:d}). ' - msg += 'Trimming first points.' + msg = f"WARN: InjCurr data is too big (>{max_:d}). " + msg += "Trimming first points." _log.warning(msg) self.update_log(msg) self.injc_data = self.injc_data[-max_:] self._update_models() - self.run_callbacks('ModelDataInjCurr-RB', value) + self.run_callbacks("ModelDataInjCurr-RB", value) return True def set_lin_ang_coeff(self, value): """.""" self.linmodel_angcoeff = value self._update_models() - self.run_callbacks('LinModAngCoeff-RB', value) + self.run_callbacks("LinModAngCoeff-RB", value) return True def set_lin_off_coeff(self, value): """.""" self.linmodel_offcoeff = value self._update_models() - self.run_callbacks('LinModOffCoeff-RB', value) + self.run_callbacks("LinModOffCoeff-RB", value) return True def set_gp_noise_std(self, value): """.""" self.gpmodel.likelihood.variance = value**2 self._update_models() - self.run_callbacks('GPModNoiseStd-RB', value) + self.run_callbacks("GPModNoiseStd-RB", value) return True def set_gp_kern_std(self, value): """.""" self.gpmodel.kern.variance = value**2 self._update_models() - self.run_callbacks('GPModKernStd-RB', value) + self.run_callbacks("GPModKernStd-RB", value) return True def set_gp_kern_leng(self, value): """.""" self.gpmodel.kern.lengthscale = value self._update_models() - self.run_callbacks('GPModKernLenScl-RB', value) + self.run_callbacks("GPModKernLenScl-RB", value) return True def set_gp_noise_std_fit(self, value): @@ -250,7 +254,7 @@ def set_gp_noise_std_fit(self, value): self.gpmodel.likelihood.variance.unfix() else: self.gpmodel.likelihood.variance.fix() - self.run_callbacks('GPModNoiseStdFit-Sts', value) + self.run_callbacks("GPModNoiseStdFit-Sts", value) return True def set_gp_kern_std_fit(self, value): @@ -259,7 +263,7 @@ def set_gp_kern_std_fit(self, value): self.gpmodel.kern.variance.unfix() else: self.gpmodel.kern.variance.fix() - self.run_callbacks('GPModKernStdFit-Sts', value) + self.run_callbacks("GPModKernStdFit-Sts", value) return True def set_gp_kern_leng_fit(self, value): @@ -268,7 +272,7 @@ def set_gp_kern_leng_fit(self, value): self.gpmodel.kern.lengthscale.unfix() else: self.gpmodel.kern.lengthscale.fix() - self.run_callbacks('GPModKernLenSclFit-Sts', value) + self.run_callbacks("GPModKernLenSclFit-Sts", value) return True # ############ Auxiliary Methods ############ @@ -282,37 +286,40 @@ def _initialize_models(self): self.bias_data = _np.linspace( self.min_bias_voltage, self.max_bias_voltage, - self.model_max_num_points) + self.model_max_num_points, + ) self.injc_data = _np_poly.polyval( - self.bias_data, self.linmodel_coeffs_inverse) + self.bias_data, self.linmodel_coeffs_inverse + ) x = self.bias_data[:, None].copy() y = self.injc_data[:, None].copy() kernel = gpy.kern.RBF(input_dim=1) - db_ = self.database['BiasFBGPModKernStd-RB'] - kernel.variance.constrain_bounded(db_['lolim']**2, db_['hilim']**2) - kernel.variance = db_['value']**2 - if bool(self.database['BiasFBGPModKernStdFit-Sts']['value']): + db_ = self.database["BiasFBGPModKernStd-RB"] + kernel.variance.constrain_bounded(db_["lolim"] ** 2, db_["hilim"] ** 2) + kernel.variance = db_["value"] ** 2 + if bool(self.database["BiasFBGPModKernStdFit-Sts"]["value"]): kernel.variance.unfix() else: kernel.variance.fix() - db_ = self.database['BiasFBGPModKernLenScl-RB'] - kernel.lengthscale.constrain_bounded(db_['lolim'], db_['hilim']) - kernel.lengthscale = db_['value'] - if bool(self.database['BiasFBGPModKernLenSclFit-Sts']['value']): + db_ = self.database["BiasFBGPModKernLenScl-RB"] + kernel.lengthscale.constrain_bounded(db_["lolim"], db_["hilim"]) + kernel.lengthscale = db_["value"] + if bool(self.database["BiasFBGPModKernLenSclFit-Sts"]["value"]): kernel.lengthscale.unfix() else: kernel.lengthscale.fix() gpmodel = gpy.models.GPRegression(x, y, kernel) - db_ = self.database['BiasFBGPModNoiseStd-RB'] + db_ = self.database["BiasFBGPModNoiseStd-RB"] gpmodel.likelihood.variance.constrain_bounded( - db_['lolim']**2, db_['hilim']**2) - gpmodel.likelihood.variance = db_['value']**2 - if bool(self.database['BiasFBGPModNoiseStdFit-Sts']['value']): + db_["lolim"] ** 2, db_["hilim"] ** 2 + ) + gpmodel.likelihood.variance = db_["value"] ** 2 + if bool(self.database["BiasFBGPModNoiseStdFit-Sts"]["value"]): gpmodel.likelihood.variance.unfix() else: gpmodel.likelihood.variance.fix() @@ -322,7 +329,7 @@ def _initialize_models(self): def _update_data(self, **kwgs): bias = self._injctrl.egun_dev.bias.voltage - dcurr = kwgs['value'] + dcurr = kwgs["value"] if None in {bias, dcurr}: return @@ -331,8 +338,8 @@ def _update_data(self, **kwgs): if bias in xun: idx = (xun == bias).nonzero()[0][0] if cnts[idx] >= max(2, self.bias_data.size // 5): - msg = 'WARN: Too many data with this abscissa. ' - msg += 'Discarding point.' + msg = "WARN: Too many data with this abscissa. " + msg += "Discarding point." _log.warning(msg) self.update_log(msg) return @@ -341,19 +348,19 @@ def _update_data(self, **kwgs): # Update models data self.bias_data = _np.r_[self.bias_data, bias] self.injc_data = _np.r_[self.injc_data, dcurr] - self.bias_data = self.bias_data[-_Const.BIASFB_MAX_DATA_SIZE:] - self.injc_data = self.injc_data[-_Const.BIASFB_MAX_DATA_SIZE:] + self.bias_data = self.bias_data[-_Const.BIASFB_MAX_DATA_SIZE :] + self.injc_data = self.injc_data[-_Const.BIASFB_MAX_DATA_SIZE :] self._update_models() def _update_models(self, force_optimize=False): x = _np.r_[self.bias_data, self.linmodel_offcoeff] y = _np.r_[self.injc_data, 0] - x = x[-self.model_max_num_points:] - y = y[-self.model_max_num_points:] + x = x[-self.model_max_num_points :] + y = y[-self.model_max_num_points :] if x.size != y.size: - msg = 'WARN: Arrays with incompatible sizes. ' - msg += 'Trimming first points of ' - msg += 'bias.' if x.size > y.size else 'injcurr.' + msg = "WARN: Arrays with incompatible sizes. " + msg += "Trimming first points of " + msg += "bias." if x.size > y.size else "injcurr." _log.warning(msg) self.update_log(msg) siz = min(x.size, y.size) @@ -361,8 +368,8 @@ def _update_models(self, force_optimize=False): y = y[-siz:] if x.size < 2: - msg = 'ERR: Too few data points. ' - msg += 'Skipping Model update.' + msg = "ERR: Too few data points. " + msg += "Skipping Model update." _log.error(msg) self.update_log(msg) return @@ -375,7 +382,8 @@ def _update_models(self, force_optimize=False): # Optimize Linear Model if do_opt and not self.use_gpmodel: self.linmodel_angcoeff = _np_poly.polyfit( - y, x - self.linmodel_offcoeff, deg=[1, ])[1] + y, x - self.linmodel_offcoeff, deg=[1] + )[1] self._npts_after_fit = 0 # update Gaussian Process Model data @@ -388,39 +396,40 @@ def _update_models(self, force_optimize=False): self.gpmodel.optimize_restarts(num_restarts=2, verbose=False) self._npts_after_fit = 0 - self.run_callbacks('ModelNrPtsAfterFit-Mon', self._npts_after_fit) + self.run_callbacks("ModelNrPtsAfterFit-Mon", self._npts_after_fit) self._update_predictions() def _update_predictions(self): gp_ = self.gpmodel self.run_callbacks( - 'GPModNoiseStd-Mon', float(gp_.likelihood.variance)**0.5) - self.run_callbacks('GPModKernStd-Mon', float(gp_.kern.variance)**0.5) - self.run_callbacks('GPModKernLenScl-Mon', float(gp_.kern.lengthscale)) - self.run_callbacks('LinModAngCoeff-Mon', self.linmodel_angcoeff) - self.run_callbacks('LinModOffCoeff-Mon', self.linmodel_offcoeff) + "GPModNoiseStd-Mon", float(gp_.likelihood.variance) ** 0.5 + ) + self.run_callbacks("GPModKernStd-Mon", float(gp_.kern.variance) ** 0.5) + self.run_callbacks("GPModKernLenScl-Mon", float(gp_.kern.lengthscale)) + self.run_callbacks("LinModAngCoeff-Mon", self.linmodel_angcoeff) + self.run_callbacks("LinModOffCoeff-Mon", self.linmodel_offcoeff) - self.run_callbacks('ModelDataBias-Mon', self.gpmodel.X.ravel()) - self.run_callbacks('ModelDataInjCurr-Mon', self.gpmodel.Y.ravel()) - self.run_callbacks('ModelNrPts-Mon', self.gpmodel.Y.size) + self.run_callbacks("ModelDataBias-Mon", self.gpmodel.X.ravel()) + self.run_callbacks("ModelDataInjCurr-Mon", self.gpmodel.Y.ravel()) + self.run_callbacks("ModelNrPts-Mon", self.gpmodel.Y.size) injcurr = _np.linspace(0, 1, 100) bias_lin = self._get_bias_voltage_linear_model(injcurr) bias_gp = self._get_bias_voltage_gpmodel(injcurr) - self.run_callbacks('LinModInferenceInjCurr-Mon', injcurr) - self.run_callbacks('LinModInferenceBias-Mon', bias_lin) - self.run_callbacks('GPModInferenceInjCurr-Mon', injcurr) - self.run_callbacks('GPModInferenceBias-Mon', bias_gp) + self.run_callbacks("LinModInferenceInjCurr-Mon", injcurr) + self.run_callbacks("LinModInferenceBias-Mon", bias_lin) + self.run_callbacks("GPModInferenceInjCurr-Mon", injcurr) + self.run_callbacks("GPModInferenceBias-Mon", bias_gp) bias = _np.linspace(self.min_bias_voltage, self.max_bias_voltage, 100) injc_lin = _np_poly.polyval(bias, self.linmodel_coeffs_inverse) injca_gp, injcs_gp = self._gpmodel_predict(bias) injcs_gp = _np.sqrt(injcs_gp) - self.run_callbacks('LinModPredBias-Mon', bias) - self.run_callbacks('LinModPredInjCurrAvg-Mon', injc_lin) - self.run_callbacks('GPModPredBias-Mon', bias) - self.run_callbacks('GPModPredInjCurrAvg-Mon', injca_gp.ravel()) - self.run_callbacks('GPModPredInjCurrStd-Mon', injcs_gp.ravel()) + self.run_callbacks("LinModPredBias-Mon", bias) + self.run_callbacks("LinModPredInjCurrAvg-Mon", injc_lin) + self.run_callbacks("GPModPredBias-Mon", bias) + self.run_callbacks("GPModPredInjCurrAvg-Mon", injca_gp.ravel()) + self.run_callbacks("GPModPredInjCurrStd-Mon", injcs_gp.ravel()) def _get_bias_voltage_gpmodel(self, dcurr): bias = self._gpmodel_infer_newx(_np.array(dcurr, ndmin=1)) diff --git a/siriuspy/siriuspy/injctrl/main.py b/siriuspy/siriuspy/injctrl/main.py index 0adcb0172..1be757097 100644 --- a/siriuspy/siriuspy/injctrl/main.py +++ b/siriuspy/siriuspy/injctrl/main.py @@ -192,10 +192,18 @@ def __init__(self): curr_pvo.add_callback(self._callback_autostop) curr_pvo.connection_callbacks.append(self._callback_conn_autostop) - self._pu_names = self._injsys_dev.handlers['as_pu'].punames - self._pu_devs = self._injsys_dev.handlers['as_pu'].pudevices + self._pu_names, self._pu_devs = list(), list() self._pu_refvolt = list() - for dev in self._pu_devs: + for idx, pun in enumerate(self._injsys_dev.handlers['as_pu'].punames): + # NOTE: The voltage of the TB pulsed magnets are used to define + # enable conditions of the egun trigger. To avoid changing the + # trigger enable status during top-up, we will not include these + # magnets in standby/warm up. + if pun.startswith('TB'): + continue + self._pu_names.append(pun) + dev = self._injsys_dev.handlers['as_pu'].pudevices[idx] + self._pu_devs.append(dev) pvo = dev.pv_object('Voltage-SP') self._pu_refvolt.append(pvo.value) pvo.add_callback(self._callback_update_pu_refvolt) @@ -513,7 +521,7 @@ def _set_egunbias(self, value): self.run_callbacks('BiasVoltCmdSts-Mon', _Const.IdleRunning.Running) self._update_log(f'Setting EGun Bias voltage to {value:.2f}V...') - if not self.egun_dev.bias.set_voltage(value, tol=0.005*value): + if not self.egun_dev.bias.set_voltage(value, tol=abs(0.005*value)): self._update_log('ERR:Could not set EGun Bias voltage.') else: self._update_log(f'Set EGun Bias voltage: {value:.2f}V.') @@ -1117,6 +1125,9 @@ def _callback_update_pumode(self, **kws): def _callback_update_pu_refvolt(self, pvname, value, **kws): if value is None: return + # do not update PU reference voltage if standby is enabled + if self._topup_pustandbyenbl == _Const.DsblEnbl.Enbl: + return if self._aspu_standby_state == _Const.StandbyInject.Standby: return devname = _PVName(pvname).device_name diff --git a/siriuspy/siriuspy/machshift/gensumm_macreport.py b/siriuspy/siriuspy/machshift/gensumm_macreport.py index 6322c95b0..ca2db8c68 100644 --- a/siriuspy/siriuspy/machshift/gensumm_macreport.py +++ b/siriuspy/siriuspy/machshift/gensumm_macreport.py @@ -7,33 +7,18 @@ # failure statistics per month intervals = [ - [Time(2021, 1, 1, 0, 0), Time(2021, 1, 31, 23, 59, 59)], - [Time(2021, 2, 1, 0, 0), Time(2021, 2, 28, 23, 59, 59)], - [Time(2021, 3, 1, 0, 0), Time(2021, 3, 31, 23, 59, 59)], - [Time(2021, 4, 1, 0, 0), Time(2021, 4, 30, 23, 59, 59)], - [Time(2021, 5, 1, 0, 0), Time(2021, 5, 31, 23, 59, 59)], - [Time(2021, 6, 1, 0, 0), Time(2021, 6, 30, 23, 59, 59)], - [Time(2021, 7, 1, 0, 0), Time(2021, 7, 31, 23, 59, 59)], - [Time(2021, 8, 1, 0, 0), Time(2021, 8, 31, 23, 59, 59)], - [Time(2021, 9, 1, 0, 0), Time(2021, 9, 30, 23, 59, 59)], - [Time(2021, 10, 1, 0, 0), Time(2021, 10, 31, 23, 59, 59)], - [Time(2021, 11, 1, 0, 0), Time(2021, 11, 30, 23, 59, 59)], - [Time(2021, 12, 1, 0, 0), Time(2021, 12, 31, 23, 59, 59)], - [Time(2022, 1, 1, 0, 0), Time(2022, 1, 31, 23, 59, 59)], - [Time(2022, 2, 1, 0, 0), Time(2022, 2, 28, 23, 59, 59)], - [Time(2022, 3, 1, 0, 0), Time(2022, 3, 31, 23, 59, 59)], - [Time(2022, 4, 1, 0, 0), Time(2022, 4, 30, 23, 59, 59)], - [Time(2022, 5, 1, 0, 0), Time(2022, 5, 31, 23, 59, 59)], - [Time(2022, 6, 1, 0, 0), Time(2022, 6, 30, 23, 59, 59)], - [Time(2022, 7, 1, 0, 0), Time(2022, 7, 31, 23, 59, 59)], - [Time(2022, 8, 1, 0, 0), Time(2022, 8, 31, 23, 59, 59)], - [Time(2022, 9, 1, 0, 0), Time(2022, 9, 30, 23, 59, 59)], - [Time(2022, 10, 1, 0, 0), Time(2022, 10, 31, 23, 59, 59)], - [Time(2022, 11, 1, 0, 0), Time(2022, 11, 30, 23, 59, 59)], - [Time(2022, 12, 1, 0, 0), Time(2022, 12, 31, 23, 59, 59)], [Time(2023, 1, 1, 0, 0), Time(2023, 1, 31, 23, 59, 59)], [Time(2023, 2, 1, 0, 0), Time(2023, 2, 28, 23, 59, 59)], [Time(2023, 3, 1, 0, 0), Time(2023, 3, 31, 23, 59, 59)], + [Time(2023, 4, 1, 0, 0), Time(2023, 4, 30, 23, 59, 59)], + [Time(2023, 5, 1, 0, 0), Time(2023, 5, 31, 23, 59, 59)], + [Time(2023, 6, 1, 0, 0), Time(2023, 6, 30, 23, 59, 59)], + [Time(2023, 7, 1, 0, 0), Time(2023, 7, 31, 23, 59, 59)], + [Time(2023, 8, 1, 0, 0), Time(2023, 8, 31, 23, 59, 59)], + [Time(2023, 9, 1, 0, 0), Time(2023, 9, 30, 23, 59, 59)], + [Time(2023, 10, 1, 0, 0), Time(2023, 10, 31, 23, 59, 59)], + [Time(2023, 11, 1, 0, 0), Time(2023, 11, 30, 23, 59, 59)], + [Time(2023, 12, 1, 0, 0), Time(2023, 12, 31, 23, 59, 59)], ] macreports = dict() @@ -48,6 +33,7 @@ progrmd, delivd, usertot = dict(), dict(), dict() currmean, currstd = dict(), dict() stable, unstable, relstable = dict(), dict(), dict() +nrbeamdump = dict() for date, macr in macreports.items(): mtbfs[date] = macr.usershift_time_between_failures_average mttrs[date] = macr.usershift_time_to_recover_average @@ -60,13 +46,15 @@ stable[date] = macr.usershift_total_stable_beam_time unstable[date] = macr.usershift_total_unstable_beam_time relstable[date] = macr.usershift_relative_stable_beam_time + nrbeamdump[date] = macr.usershift_beam_dump_count -str_ = '{:<10s}' + '{:>16s}'*9 + '{:>20s}' +str_ = '{:<10s}' + '{:>16s}'*10 + '{:>20s}' print(str_.format( 'Y-M', 'MTBF', 'MTTR', 'Reliability', 'Progrmd hours', 'Delivd hours', 'Total hours', - '% stable hours', 'Stable hours', 'Unstable hours', 'Current (Avg±Std)')) -str_ = '{:<10s}' + ' {:>12.3f}'*9 + ' {:4.3f} ± {:4.3f}' + '% stable hours', 'Stable hours', 'Unstable hours', '# Beam Dump', + 'Current (Avg±Std)')) +str_ = '{:<10s}' + ' {:>12.3f}'*10 + ' {:4.3f} ± {:4.3f}' for date in macreports: print(str_.format( str(date.year)+'-'+str(date.month), @@ -79,6 +67,7 @@ relstable[date], stable[date], unstable[date], + nrbeamdump[date], currmean[date], currstd[date], )) @@ -105,16 +94,17 @@ # programmed vs. delivered hours macr = MacReport() macr.connector.timeout = 300 -macr.time_start = Time(2022, 4, 1, 0, 0) -macr.time_stop = Time(2023, 3, 31, 23, 59, 59) +macr.time_start = Time(2023, 1, 1, 0, 0) +macr.time_stop = Time(2023, 12, 31, 23, 59, 59) macr.update() -str_ = '{:<10s}' + '{:>16s}'*9 + '{:>20s}' +str_ = '{:<10s}' + '{:>16s}'*10 + '{:>20s}' print(str_.format( 'Y-M', 'MTBF', 'MTTR', 'Reliability', 'Progrmd hours', 'Delivd hours', 'Total hours', - '% stable hours', 'Stable hours', 'Unstable hours', 'Current (Avg±Std)')) -str_ = '{:<10s}' + ' {:>12.3f}'*9 + ' {:4.3f} ± {:4.3f}' + '% stable hours', 'Stable hours', 'Unstable hours', '# Beam Dump', + 'Current (Avg±Std)')) +str_ = '{:<10s}' + ' {:>12.3f}'*10 + ' {:4.3f} ± {:4.3f}' print( str_.format( str(macr.time_start.year)+'-'+str(macr.time_start.month) + ' a ' + @@ -128,6 +118,7 @@ macr.usershift_relative_stable_beam_time, macr.usershift_total_stable_beam_time, macr.usershift_total_unstable_beam_time, + macr.usershift_beam_dump_count, macr.usershift_current_average, macr.usershift_current_stddev, )) @@ -154,31 +145,3 @@ plt.legend(loc=4) plt.title('Integrated User Hours') fig.show() - -new_dates, new_dtimes_progmd, new_dtimes_deliv = [], [], [] -for i, d in enumerate(dates): - if not new_dates or d.date() != new_dates[-1].date(): - new_dates.append(d) - new_dtimes_progmd.append(dtimes_users_progmd[i]) - new_dtimes_deliv.append(dtimes_users_impltd[i]) - else: - new_dtimes_progmd[-1] += dtimes_users_progmd[i] - new_dtimes_deliv[-1] += dtimes_users_impltd[i] -new_cum_progmd = np.cumsum(new_dtimes_progmd) -new_cum_deliv = np.cumsum(new_dtimes_deliv) - -fig = plt.figure() -axs = plt.gca() -axs.xaxis.axis_date() -axs.plot(new_dates, new_cum_progmd, '-', label='Programmed') -axs.plot(new_dates, new_cum_deliv, '-', label='Delivered') -axs.grid() -axs.set_ylabel('Integrated Hours') -plt.legend(loc=4) -plt.title('Integrated User Hours') -fig.show() - -np.savetxt( - 'integrated_user_hours_2021.txt', - np.c_[[d.timestamp() for d in new_dates], - new_cum_progmd, new_cum_deliv]) diff --git a/siriuspy/siriuspy/machshift/macreport.py b/siriuspy/siriuspy/machshift/macreport.py index 554df9515..67cf16bb9 100644 --- a/siriuspy/siriuspy/machshift/macreport.py +++ b/siriuspy/siriuspy/machshift/macreport.py @@ -254,6 +254,8 @@ class MacReport: ] FAILURES_MANUAL = [ + # hydraulic failure, wrong machine shift for recovery + [_Time(2023, 3, 3, 22, 56, 0, 0), _Time(2023, 3, 3, 23, 0, 0, 0)], # power grid failure, archiver was down [_Time(2023, 5, 18, 5, 55, 0, 0), _Time(2023, 5, 18, 9, 8, 0, 0)], ] @@ -1607,10 +1609,13 @@ def _calc_beam_for_users_stats(self): self._usershift_current_end_stddev = 0 # # # ----- failures ----- - beam_dump_values = _np.logical_not( - self._raw_data['Failures']['WrongShift']) * \ - self._raw_data['Failures']['ManualAnnotated'] * \ - self._raw_data['Failures']['NoEBeam'] + beam_dump_values = 1 * _np.logical_and( + _np.logical_not( + self._raw_data['Failures']['WrongShift'] + ), _np.logical_or( + self._raw_data['Failures']['ManualAnnotated'], + self._raw_data['Failures']['NoEBeam'] + )) self._usershift_beam_dump_count = _np.sum( _np.diff(beam_dump_values) > 0) diff --git a/siriuspy/siriuspy/machshift/savedata_macreport.py b/siriuspy/siriuspy/machshift/savedata_macreport.py index 9bfbaa907..2a8848e51 100644 --- a/siriuspy/siriuspy/machshift/savedata_macreport.py +++ b/siriuspy/siriuspy/machshift/savedata_macreport.py @@ -5,8 +5,8 @@ from siriuspy.machshift.macreport import MacReport # determine interval -time_start = Time(2022, 1, 1, 0, 0) -time_stop = Time(2022, 12, 31, 23, 59, 59) +time_start = Time(2023, 1, 1, 0, 0) +time_stop = Time(2023, 12, 31, 23, 59, 59) # get data from interval macr = MacReport() @@ -16,12 +16,13 @@ macr.update() # print small report -str_ = '{:<10s}' + '{:>16s}'*9 + '{:>20s}' +str_ = '{:<10s}' + '{:>16s}'*10 + '{:>20s}' print(str_.format( 'Y-M', 'MTBF', 'MTTR', 'Reliability', 'Progrmd hours', 'Delivd hours', 'Total hours', - '% stable hours', 'Stable hours', 'Unstable hours', 'Current (Avg±Std)')) -str_ = '{:<10s}' + ' {:>12.3f}'*9 + ' {:4.3f} ± {:4.3f}' + '% stable hours', 'Stable hours', 'Unstable hours', '# Beam Dump', + 'Current (Avg±Std)')) +str_ = '{:<10s}' + ' {:>12.3f}'*10 + ' {:4.3f} ± {:4.3f}' print( str_.format( str(macr.time_start.year)+'-'+str(macr.time_start.month) + ' a ' + @@ -35,6 +36,7 @@ macr.usershift_relative_stable_beam_time, macr.usershift_total_stable_beam_time, macr.usershift_total_unstable_beam_time, + macr.usershift_beam_dump_count, macr.usershift_current_average, macr.usershift_current_stddev, )) @@ -50,18 +52,20 @@ raw_data['Failures - SubsystemsNOk'] = np.asarray(failures['SubsystemsNOk'], dtype=int) raw_data['Failures - NoEBeam'] = np.asarray(failures['NoEBeam'], dtype=int) raw_data['Failures - WrongShift'] = np.asarray(failures['WrongShift'], dtype=int) +raw_data['Failures - ManualAnnotated'] = np.asarray(failures['ManualAnnotated'], dtype=int) raw_data['TimeDate'] = [Time(t).get_iso8601() for t in raw_data['Timestamp']] # stored data in specific order order = [ 'Timestamp', 'TimeDate', 'Current', - 'Failures - SubsystemsNOk', 'Failures - NoEBeam', 'Failures - WrongShift', + 'Failures - SubsystemsNOk', 'Failures - NoEBeam', + 'Failures - WrongShift', 'Failures - ManualAnnotated', 'UserShiftProgmd', 'UserShiftInitCurr', 'UserShiftDelivd'] data = {k: raw_data[k] for k in order} # save data to csv df = pd.DataFrame.from_dict(data) -df.to_csv('raw_data_machine_report_2022.csv', index=False, header=True) +df.to_csv('raw_data_machine_report_2023.csv', index=False, header=True) # check hour numbers: each point is equivalent to 5s, converting to hours programmed = sum(data['UserShiftProgmd'])*5/60/60 diff --git a/siriuspy/siriuspy/optics/lattice_survey.py b/siriuspy/siriuspy/optics/lattice_survey.py index 30c94c905..e3c708b9a 100644 --- a/siriuspy/siriuspy/optics/lattice_survey.py +++ b/siriuspy/siriuspy/optics/lattice_survey.py @@ -237,7 +237,7 @@ def _get_all_bpms(): line = line.strip() if not line or line[0] == '#': continue # empty line - _, dev, *_ = line.split() + dev, *_ = line.split() dev = _PVName(dev) if dev.dev in ('BPM', 'PBPM'): bpms.add(dev) diff --git a/siriuspy/siriuspy/orbintlk/__init__.py b/siriuspy/siriuspy/orbintlk/__init__.py new file mode 100644 index 000000000..dc29d2c3b --- /dev/null +++ b/siriuspy/siriuspy/orbintlk/__init__.py @@ -0,0 +1 @@ +"""Orbit Interlock subpackage.""" diff --git a/siriuspy/siriuspy/orbintlk/csdev.py b/siriuspy/siriuspy/orbintlk/csdev.py new file mode 100644 index 000000000..f5c998cf7 --- /dev/null +++ b/siriuspy/siriuspy/orbintlk/csdev.py @@ -0,0 +1,421 @@ +"""Define PVs, constants and properties of High Level Orbit Interlock app.""" + +import os as _os +import numpy as _np + +from .. import csdev as _csdev +from ..util import ClassProperty as _classproperty +from ..search import BPMSearch as _BPMSearch, LLTimeSearch as _LLTimeSearch, \ + HLTimeSearch as _HLTimeSearch +from ..namesys import SiriusPVName as _PVName +from ..diagbeam.bpm.csdev import Const as _csbpm + + +NR_BPM = 160 + + +# --- Enumeration Types --- + +class ETypes(_csdev.ETypes): + """Local enumerate types.""" + + STS_LBLS_BPM = ( + 'Connected', + 'PosEnblSynced', 'AngEnblSynced', 'MinSumEnblSynced', 'GlobEnblSynced', + 'PosLimsSynced', 'AngLimsSynced', 'MinSumLimsSynced', + 'AcqConfigured', 'LogTrigConfig') + STS_LBLS_TIMING = ( + 'EVGConn', 'EVGIntlkEnblSynced', 'EVGConfig', + 'FoutsConn', 'FoutsConfig', + 'AFCTimingConn', 'AFCTimingConfig', + 'AFCPhysTrigsConn', 'AFCPhysTrigsConfig', + 'OrbIntlkTrigConn', 'OrbIntlkTrigStatusOK', 'OrbIntlkTrigConfig', + 'OrbIntlkRedTrigConn', 'OrbIntlkRedTrigStatusOK', + 'OrbIntlkRedTrigConfig', + 'LLRFPsMtmTrigConn', 'LLRFPsMtmTrigStatusOK', 'LLRFPsMtmTrigConfig', + 'BPMPsMtmTrigConn', 'BPMPsMtmTrigStatusOK', 'BPMPsMtmTrigConfig', + 'DCCT13C4PsMtmTrigConn', 'DCCT13C4PsMtmTrigStatusOK', + 'DCCT13C4PsMtmTrigConfig', 'DCCT14C4PsMtmTrigConn', + 'DCCT14C4PsMtmTrigStatusOK', 'DCCT14C4PsMtmTrigConfig', + ) + STS_LBLS_LLRF = ('Connected', 'Configured') + + +_et = ETypes # syntactic sugar + + +# --- Const class --- + +class Const(_csdev.Const): + """Const class defining Orbit Interlock constants.""" + + CONV_UM_2_NM = 1e3 + + IOC_PREFIX = _PVName('SI-Glob:AP-OrbIntlk') + DEF_TIMEOUT = 10 # [s] + DEF_TIMESLEEP = 0.1 # [s] + DEF_TIMEWAIT = 3 # [s] + + DEF_TIME2WAIT_INTLKREARM = 30 # [s] + + HLTRIG_2_CONFIG = [ + ('SI-Fam:TI-BPM-OrbIntlk', ( + ('Src-Sel', 4), + ('DelayRaw-SP', 0), + ('State-Sel', 1), + ('WidthRaw-SP', 1), + ('Direction-Sel', 1))), + ('SI-Fam:TI-OrbIntlkRedundancy', ( + ('Src-Sel', 0), + ('State-Sel', 1), + ('Polarity-Sel', 0), + ('Log-Sel', 1))), + ('SI-Glob:TI-LLRF-PsMtm', ( + ('Src-Sel', 5), + ('DelayRaw-SP', 0), + ('State-Sel', 1), + ('WidthRaw-SP', 9369))), + ('SI-Fam:TI-BPM-PsMtm', ( + ('Src-Sel', 5), + ('DelayRaw-SP', 0), + ('State-Sel', 1), + ('WidthRaw-SP', 6))), + ('SI-13C4:TI-DCCT-PsMtm', ( + ('Src-Sel', 0), + ('State-Sel', 1), + ('Polarity-Sel', 0), + ('Log-Sel', 1))), + ('SI-14C4:TI-DCCT-PsMtm', ( + ('Src-Sel', 0), + ('State-Sel', 1), + ('Polarity-Sel', 0), + ('Log-Sel', 1))), + ] + FOUTSFIXED_RXENBL = { + 'CA-RaTim:TI-Fout-2': 0b01000001, + } + AFCTI_CONFIGS = ( + ('DevEnbl-Sel', 1), + ('RTMPhasePropGain-SP', 100), + ('RTMPhaseIntgGain-SP', 1), + ('RTMFreqPropGain-SP', 1), + ('RTMFreqIntgGain-SP', 128), + ('RTMPhaseNavg-SP', 0), + ('RTMPhaseDiv-SP', 0), + ) + AFCPHYTRIG_CONFIGS = ( + ('Dir-Sel', 0), + ('DirPol-Sel', 1), + ('TrnLen-SP', 20), + ) + SIBPMLOGTRIG_CONFIGS = ( + ('TRIGGER4TrnSrc-Sel', 1), + ('TRIGGER4TrnOutSel-SP', 2), + ('TRIGGER_PM0RcvSrc-Sel', 0), + ('TRIGGER_PM0RcvInSel-SP', 2), + ('TRIGGER_PM1RcvSrc-Sel', 0), + ('TRIGGER_PM1RcvInSel-SP', 2), + ('TRIGGER_PM6RcvSrc-Sel', 0), + ('TRIGGER_PM6RcvInSel-SP', 2), + ('TRIGGER_PM7RcvSrc-Sel', 0), + ('TRIGGER_PM7RcvInSel-SP', 2), + ('TRIGGER_PM11RcvSrc-Sel', 0), + ('TRIGGER_PM11RcvInSel-SP', 2), + ('TRIGGER_PM12RcvSrc-Sel', 0), + ('TRIGGER_PM12RcvInSel-SP', 2), + ('TRIGGER_PM14RcvSrc-Sel', 0), + ('TRIGGER_PM14RcvInSel-SP', 2), + ) + REDUNDANCY_TABLE = { + 'IA-10RaBPM:TI-AMCFPGAEVR': 'IA-10RaBPM:TI-EVR', + } + + __EVG_CONFIGS = None + __FOUTS_2_MON = None + + @_classproperty + def EVG_CONFIGS(cls): + """EVG configurations""" + if cls.__EVG_CONFIGS is not None: + return cls.__EVG_CONFIGS + + hltg_enbl = [ + 'SI-Fam:TI-BPM-OrbIntlk', + 'SI-Fam:TI-OrbIntlkRedundancy', + ] + lltg_enbl = [] + for hltg in hltg_enbl: + lltg_enbl.extend(_HLTimeSearch.get_ll_trigger_names(hltg)) + lltg_enbl = set(lltg_enbl) + + fouts = set() + evgchans = set() + evgrxenbl = set() + for lltg in lltg_enbl: + fch = _LLTimeSearch.get_fout_channel(lltg) + fouts.add(fch.device_name) + evgch = _LLTimeSearch.get_evg_channel(fch) + evgchans.add(evgch) + evgrxenbl.add(int(evgch.propty[3:])) + evgrxenbl = sorted(evgrxenbl) + + hlevts = _HLTimeSearch.get_hl_events() + evtin0 = int(hlevts['Intlk'].strip('Evt')) + evtin1 = int(hlevts['ItlkR'].strip('Evt')) + evtin2 = int(hlevts['DCT13'].strip('Evt')) + evtin3 = int(hlevts['DCT14'].strip('Evt')) + evtout = int(hlevts['RFKll'].strip('Evt')) + evgconfigs = [ + ('IntlkTbl0to15-Sel', 0b000000010000001), + ('IntlkTbl16to27-Sel', 0), + ('IntlkCtrlRepeat-Sel', 0), + ('IntlkCtrlRepeatTime-SP', 0), + ('IntlkEvtIn0-SP', evtin0), + ('IntlkEvtIn1-SP', evtin1), + ('IntlkEvtIn2-SP', evtin2), + ('IntlkEvtIn3-SP', evtin3), + ('IntlkEvtOut-SP', evtout), + ('IntlkLogEnbl-SP', 0b11111111), + ] + evgconfigs.extend([(f'RxEnbl-SP.B{b}', 1) for b in evgrxenbl]) + + cls.__FOUTS_2_MON = fouts + cls.__EVG_CONFIGS = evgconfigs + + return cls.__EVG_CONFIGS + + @_classproperty + def FOUTS_2_MON(cls): + """Fouts to be monitored.""" + cls.EVG_CONFIGS + return cls.__FOUTS_2_MON + + AcqChan = _csbpm.AcqChan + AcqTrigTyp = _csbpm.AcqTrigTyp + AcqRepeat = _csbpm.AcqRepeat + AcqStates = _csbpm.AcqStates + + def __init__(self): + """Class constructor.""" + # crates mapping + self.crates_map = _LLTimeSearch.get_crates_mapping() + + # trigger source to fout out mapping + self.trigsrc2fout_map = _LLTimeSearch.get_trigsrc2fout_mapping() + + # interlock redundancy table for fout outs + self.intlkr_fouttable = { + self.trigsrc2fout_map[k]: self.trigsrc2fout_map[v] + for k, v in self.REDUNDANCY_TABLE.items()} + self.intlkr_fouttable.update( + {v: k for k, v in self.intlkr_fouttable.items()}) + + # bpm names and nicknames + self.bpm_names = _BPMSearch.get_names({'sec': 'SI', 'dev': 'BPM'}) + if NR_BPM != len(self.bpm_names): + raise ValueError('Inconsistent NR_BPM parameter!') + self.bpm_nicknames = _BPMSearch.get_nicknames(self.bpm_names) + self.bpm_idcs = {b: idx for idx, b in enumerate(self.bpm_names)} + + # bpm position along the ring + self.bpm_pos = _BPMSearch.get_positions(self.bpm_names) + + # bpm distance for each BPM pair + aux_pos = _np.roll(self.bpm_pos, 1) + bpm_dist = _np.diff(aux_pos)[::2] + # copy same distance from next high beta section to injection section + bpm_dist[0] = bpm_dist[4*4] + bpm_dist = _np.repeat(bpm_dist, 2) + self.bpm_dist = _np.roll(bpm_dist, -1) + + # bpm number + self.nr_bpms = len(self.bpm_names) + + # data folder + path = _os.path.join( + '/home', 'sirius', 'iocs-log', 'si-ap-orbintlk', 'data') + self.pos_enbl_fname = _os.path.join(path, 'pos_enbllist.enbl') + self.ang_enbl_fname = _os.path.join(path, 'ang_enbllist.enbl') + self.minsum_enbl_fname = _os.path.join(path, 'minsum_enbllist.enbl') + + self.pos_x_min_lim_fname = _os.path.join(path, 'pos_x_min.lim') + self.pos_x_max_lim_fname = _os.path.join(path, 'pos_x_max.lim') + self.pos_y_min_lim_fname = _os.path.join(path, 'pos_y_min.lim') + self.pos_y_max_lim_fname = _os.path.join(path, 'pos_y_max.lim') + self.ang_x_min_lim_fname = _os.path.join(path, 'ang_x_min.lim') + self.ang_x_max_lim_fname = _os.path.join(path, 'ang_x_max.lim') + self.ang_y_min_lim_fname = _os.path.join(path, 'ang_y_min.lim') + self.ang_y_max_lim_fname = _os.path.join(path, 'ang_y_max.lim') + self.minsum_lim_fname = _os.path.join(path, 'minsum.lim') + + def get_database(self): + """Return Soft IOC database.""" + pvs_database = { + # Global + 'Version-Cte': {'type': 'string', 'value': 'UNDEF'}, + 'Log-Mon': {'type': 'string', 'value': 'Starting...'}, + + 'Enable-Sel': { + 'type': 'enum', 'enums': _et.DSBL_ENBL, + 'value': self.DsblEnbl.Dsbl}, + 'Enable-Sts': { + 'type': 'enum', 'enums': _et.DSBL_ENBL, + 'value': self.DsblEnbl.Dsbl}, + 'BPMStatus-Mon': {'type': 'int', 'value': 0b111111111}, + 'TimingStatus-Mon': {'type': 'int', 'value': (1 << 19) - 1}, + 'LLRFStatus-Mon': {'type': 'int', 'value': 0b11}, + 'BPMStatusLabels-Cte': { + 'type': 'string', 'count': len(_et.STS_LBLS_BPM), + 'value': _et.STS_LBLS_BPM}, + 'TimingStatusLabels-Cte': { + 'type': 'string', 'count': len(_et.STS_LBLS_TIMING), + 'value': _et.STS_LBLS_TIMING}, + 'LLRFStatusLabels-Cte': { + 'type': 'string', 'count': len(_et.STS_LBLS_LLRF), + 'value': _et.STS_LBLS_LLRF}, + 'BPMMonitoredDevices-Mon': { + 'type': 'char', 'count': 1000, 'value': ''}, + 'TimingMonitoredDevices-Mon': { + 'type': 'char', 'count': 1000, 'value': ''}, + + # Enable lists + 'PosEnblList-SP': { + 'type': 'int', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'BPM used in orbit position interlock'}, + 'PosEnblList-RB': { + 'type': 'int', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'BPM used in orbit position interlock'}, + + 'AngEnblList-SP': { + 'type': 'int', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'BPM used in orbit angle interlock'}, + 'AngEnblList-RB': { + 'type': 'int', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'BPM used in orbit angle interlock'}, + + 'MinSumEnblList-SP': { + 'type': 'int', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'BPM used with minimum sum threshold enabled'}, + 'MinSumEnblList-RB': { + 'type': 'int', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'BPM used with minimum sum threshold enabled'}, + + # Limits + 'PosXMinLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position minimum limits for X'}, + 'PosXMinLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position minimum limits for X'}, + 'PosXMaxLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position maximum limits for X'}, + 'PosXMaxLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position maximum limits for X'}, + + 'PosYMinLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position minimum limits for Y'}, + 'PosYMinLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position minimum limits for Y'}, + 'PosYMaxLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position maximum limits for Y'}, + 'PosYMaxLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'position maximum limits for Y'}, + + 'AngXMinLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle minimum limits for X'}, + 'AngXMinLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle minimum limits for X'}, + 'AngXMaxLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle maximum limits for X'}, + 'AngXMaxLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle maximum limits for X'}, + + 'AngYMinLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle minimum limits for Y'}, + 'AngYMinLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle minimum limits for Y'}, + 'AngYMaxLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle maximum limits for Y'}, + 'AngYMaxLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], + 'unit': 'angle maximum limits for Y'}, + + 'MinSumLim-SP': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], 'unit': 'minimum sum limits'}, + 'MinSumLim-RB': { + 'type': 'float', 'count': self.nr_bpms, + 'value': self.nr_bpms*[0], 'unit': 'minimum sum limits'}, + + # Reset + 'ResetBPMGen-Cmd': {'type': 'int', 'value': 0}, + 'ResetBPMPos-Cmd': {'type': 'int', 'value': 0}, + 'ResetBPMAng-Cmd': {'type': 'int', 'value': 0}, + 'ResetBPM-Cmd': {'type': 'int', 'value': 0}, + 'Reset-Cmd': {'type': 'int', 'value': 0}, + 'ResetTimingLockLatches-Cmd': {'type': 'int', 'value': 0}, + 'ResetAFCTimingRTMClk-Cmd': {'type': 'int', 'value': 0}, + + # Acquisition + 'PsMtmAcqChannel-Sel': { + 'type': 'enum', 'value': self.AcqChan.TbT, + 'enums': self.AcqChan._fields}, + 'PsMtmAcqChannel-Sts': { + 'type': 'enum', 'value': self.AcqChan.TbT, + 'enums': self.AcqChan._fields}, + 'PsMtmAcqSamplesPre-SP': { + 'type': 'int', 'value': 20000, 'lolim': 0, 'hilim': 1_000_000}, + 'PsMtmAcqSamplesPre-RB': { + 'type': 'int', 'value': 20000, 'lolim': 0, 'hilim': 1_000_000}, + 'PsMtmAcqSamplesPost-SP': { + 'type': 'int', 'value': 20000, 'lolim': 0, 'hilim': 1_000_000}, + 'PsMtmAcqSamplesPost-RB': { + 'type': 'int', 'value': 20000, 'lolim': 0, 'hilim': 1_000_000}, + 'PsMtmAcqConfig-Cmd': {'type': 'int', 'value': 0}, + + # Config devices + 'ConfigEVG-Cmd': {'type': 'int', 'value': 0}, + 'ConfigFouts-Cmd': {'type': 'int', 'value': 0}, + 'ConfigAFCTiming-Cmd': {'type': 'int', 'value': 0}, + 'ConfigHLTriggers-Cmd': {'type': 'int', 'value': 0}, + 'ConfigLLRFIntlk-Cmd': {'type': 'int', 'value': 0}, + 'ConfigBPMs-Cmd': {'type': 'int', 'value': 0}, + 'ConfigAFCPhyTrigs-Cmd': {'type': 'int', 'value': 0}, + } + pvs_database = _csdev.add_pvslist_cte(pvs_database) + return pvs_database diff --git a/siriuspy/siriuspy/orbintlk/main.py b/siriuspy/siriuspy/orbintlk/main.py new file mode 100644 index 000000000..60c7c0c2b --- /dev/null +++ b/siriuspy/siriuspy/orbintlk/main.py @@ -0,0 +1,1661 @@ +"""High Level Orbit Interlock main application.""" + +import os as _os +import logging as _log +import time as _time +from functools import partial as _part + +import numpy as _np + +from ..util import update_bit as _updt_bit, get_bit as _get_bit +from ..namesys import SiriusPVName as _PVName +from ..search import LLTimeSearch as _LLTimeSearch, \ + HLTimeSearch as _HLTimeSearch +from ..thread import RepeaterThread as _Repeat, \ + LoopQueueThread as _LoopQueueThread +from ..epics import CAThread as _CAThread +from ..callbacks import Callback as _Callback +from ..devices import OrbitInterlock as _OrbitIntlk, FamBPMs as _FamBPMs, \ + EVG as _EVG, ASLLRF as _ASLLRF, Trigger as _Trigger, Device as _Device, \ + SOFB as _SOFB, HLFOFB as _FOFB, AFCPhysicalTrigger as _AFCPhysicalTrigger + +from .csdev import Const as _Const, ETypes as _ETypes + + +class App(_Callback): + """High Level Orbit Interlock main application.""" + + SCAN_FREQUENCY = 1 # [Hz] + + def __init__(self, tests=False): + """Class constructor.""" + super().__init__() + self._is_dry_run = tests + self._const = _Const() + self._pvs_database = self._const.get_database() + self._init = False + + # internal states + self._llrf_intlk_state = 0b111011 if self._is_dry_run else 0b000000 + self._state = self._const.OffOn.Off + self._bpm_status = self._pvs_database['BPMStatus-Mon']['value'] + self._timing_status = self._pvs_database['TimingStatus-Mon']['value'] + self._enable_lists = { + 'pos': _np.zeros(self._const.nr_bpms, dtype=bool), + 'ang': _np.zeros(self._const.nr_bpms, dtype=bool), + 'minsum': _np.zeros(self._const.nr_bpms, dtype=bool), + } + self._limits = { + 'pos_x_min': _np.zeros(self._const.nr_bpms, dtype=int), + 'pos_x_max': _np.zeros(self._const.nr_bpms, dtype=int), + 'pos_y_min': _np.zeros(self._const.nr_bpms, dtype=int), + 'pos_y_max': _np.zeros(self._const.nr_bpms, dtype=int), + 'ang_x_min': _np.zeros(self._const.nr_bpms, dtype=int), + 'ang_x_max': _np.zeros(self._const.nr_bpms, dtype=int), + 'ang_y_min': _np.zeros(self._const.nr_bpms, dtype=int), + 'ang_y_max': _np.zeros(self._const.nr_bpms, dtype=int), + 'minsum': _np.zeros(self._const.nr_bpms, dtype=int), + } + self._acq_chan = self._pvs_database['PsMtmAcqChannel-Sel']['value'] + self._acq_spre = self._pvs_database['PsMtmAcqSamplesPre-SP']['value'] + self._acq_spost = self._pvs_database['PsMtmAcqSamplesPost-SP']['value'] + self._thread_acq = None + self._thread_cbevgilk = None + self._thread_cbevgrx = None + self._thread_cbbpm = None + self._bpm_mon_devs = list() + self._ti_mon_devs = list() + self._lock_threads = dict() + self._lock_failures = set() + self._lock_suspend = True + self._set_queue = _LoopQueueThread() + self._set_queue.start() + + # devices and connections + # # EVG + self._evg_dev = _EVG(props2init=[ + 'IntlkCtrlEnbl-Sel', 'IntlkCtrlEnbl-Sts', + 'IntlkCtrlRst-Sel', 'IntlkCtrlRst-Sts', + 'IntlkCtrlRepeat-Sel', 'IntlkCtrlRepeat-Sts', + 'IntlkCtrlRepeatTime-SP', 'IntlkCtrlRepeatTime-RB', + 'IntlkTbl0to15-Sel', 'IntlkTbl0to15-Sts', + 'IntlkTbl16to27-Sel', 'IntlkTbl16to27-Sts', + 'IntlkEvtIn0-SP', 'IntlkEvtIn0-RB', + 'IntlkEvtOut-SP', 'IntlkEvtOut-SP', + 'IntlkEvtStatus-Mon', + 'RxEnbl-SP', 'RxEnbl-RB', + 'RxEnbl-SP.B0', 'RxEnbl-RB.B0', + 'RxEnbl-SP.B1', 'RxEnbl-RB.B1', + 'RxEnbl-SP.B2', 'RxEnbl-RB.B2', + 'RxEnbl-SP.B3', 'RxEnbl-RB.B3', + 'RxEnbl-SP.B4', 'RxEnbl-RB.B4', + 'RxEnbl-SP.B5', 'RxEnbl-RB.B5', + 'RxEnbl-SP.B6', 'RxEnbl-RB.B6', + 'RxEnbl-SP.B7', 'RxEnbl-RB.B7', + 'RxLockedLtc-Mon', 'RxLockedLtcRst-Cmd', + ]) + # interlock callback + pvo = self._evg_dev.pv_object('IntlkEvtStatus-Mon') + pvo.auto_monitor = True + pvo.add_callback(self._callback_evg_intlk) + pvo.connection_callbacks.append(self._conn_callback_timing) + # rxlock callback + pvo = self._evg_dev.pv_object('RxLockedLtc-Mon') + pvo.auto_monitor = True + pvo.add_callback(self._callback_evg_rxlock) + + # # Fouts + foutnames = list( + self._const.FOUTS_2_MON | + self._const.FOUTSFIXED_RXENBL.keys() + ) + self._thread_cbfout = {fout: None for fout in foutnames} + self._fout_devs = { + devname: _Device( + devname, + props2init=[ + 'RxEnbl-SP', 'RxEnbl-RB', + 'RxLockedLtc-Mon', 'RxLockedLtcRst-Cmd', + ], auto_monitor_mon=True) + for devname in foutnames} + self._fout2rxenbl = dict() + for devname, dev in self._fout_devs.items(): + pvo = dev.pv_object('RxLockedLtc-Mon') + pvo.add_callback(self._callback_fout_rxlock) + pvo.connection_callbacks.append(self._conn_callback_timing) + rxenbl = self._const.FOUTSFIXED_RXENBL.get(devname, 0) + self._fout2rxenbl[devname] = rxenbl + + # # AFC timing + self._afcti_devs = { + idx+1: _Device( + f'IA-{idx+1:02}RaBPM:TI-AMCFPGAEVR', + props2init=[ + 'DevEnbl-Sel', 'DevEnbl-Sts', + 'RTMClkLockedLtc-Mon', 'ClkLockedLtcRst-Cmd', + 'RTMClkRst-Cmd', + 'RTMPhasePropGain-SP', 'RTMPhasePropGain-RB', + 'RTMPhaseIntgGain-SP', 'RTMPhaseIntgGain-RB', + 'RTMFreqPropGain-SP', 'RTMFreqPropGain-RB', + 'RTMFreqIntgGain-SP', 'RTMFreqIntgGain-RB', + 'RTMPhaseNavg-SP', 'RTMPhaseNavg-RB', + 'RTMPhaseDiv-SP', 'RTMPhaseDiv-RB', + ], auto_monitor_mon=True) + for idx in range(20) + } + for dev in self._afcti_devs.values(): + pvo = dev.pv_object('RTMClkLockedLtc-Mon') + pvo.connection_callbacks.append(self._conn_callback_timing) + + # # RF EVE + trgsrc = _HLTimeSearch.get_ll_trigger_names('SI-Glob:TI-LLRF-PsMtm') + pvname = _LLTimeSearch.get_channel_output_port_pvname(trgsrc[0]) + self._llrf_evtcnt_pvname = f'{pvname.propty}EvtCnt-Mon' + self._everf_dev = _Device( + pvname.device_name, + props2init=[self._llrf_evtcnt_pvname, ], + auto_monitor_mon=True) + pvo = self._everf_dev.pv_object(self._llrf_evtcnt_pvname) + pvo.wait_for_connection() + self._everf_evtcnt = pvo.get() or 0 + + # # HL triggers + self._hltrig_devs = dict() + for trigname, configs in self._const.HLTRIG_2_CONFIG: + props2init = list() + for prop, _ in configs: + props2init.append(prop) + props2init.append(_PVName.from_sp2rb(prop)) + props2init.append('Status-Mon') + self._hltrig_devs[trigname] = _Trigger( + trigname=trigname, + props2init=props2init, + auto_monitor_mon=True) + if 'LLRF' in trigname or 'OrbIntlkRedundancy' in trigname: + pvo = self._hltrig_devs[trigname].pv_object('Status-Mon') + pvo.add_callback(self._callback_hltrig_status) + + # # BPM devices + self._orbintlk_dev = _OrbitIntlk() + for dev in self._orbintlk_dev.devices: + pvo = dev.pv_object('IntlkLtc-Mon') + pvo.auto_monitor = True + pvo.add_callback(self._callback_bpm_intlk) + + self._fambpm_dev = _FamBPMs( + devname=_FamBPMs.DEVICES.SI, ispost_mortem=True, + props2init=[ + 'ACQChannel-Sel', 'ACQChannel-Sts', + 'ACQSamplesPre-SP', 'ACQSamplesPre-RB', + 'ACQSamplesPost-SP', 'ACQSamplesPost-RB', + 'ACQTriggerRep-Sel', 'ACQTriggerRep-Sts', + 'ACQTrigger-Sel', 'ACQTrigger-Sts', + 'ACQTriggerEvent-Sel', 'ACQTriggerEvent-Sts', + 'ACQStatus-Sts', + 'INFOFAcqRate-RB', 'INFOMONITRate-RB', + 'TRIGGER4TrnSrc-Sel', 'TRIGGER4TrnSrc-Sts', + 'TRIGGER4TrnOutSel-SP', 'TRIGGER4TrnOutSel-RB', + 'TRIGGER_PM0RcvSrc-Sel', 'TRIGGER_PM0RcvSrc-Sts', + 'TRIGGER_PM0RcvInSel-SP', 'TRIGGER_PM0RcvInSel-RB', + 'TRIGGER_PM1RcvSrc-Sel', 'TRIGGER_PM1RcvSrc-Sts', + 'TRIGGER_PM1RcvInSel-SP', 'TRIGGER_PM1RcvInSel-RB', + 'TRIGGER_PM6RcvSrc-Sel', 'TRIGGER_PM6RcvSrc-Sts', + 'TRIGGER_PM6RcvInSel-SP', 'TRIGGER_PM6RcvInSel-RB', + 'TRIGGER_PM7RcvSrc-Sel', 'TRIGGER_PM7RcvSrc-Sts', + 'TRIGGER_PM7RcvInSel-SP', 'TRIGGER_PM7RcvInSel-RB', + 'TRIGGER_PM11RcvSrc-Sel', 'TRIGGER_PM11RcvSrc-Sts', + 'TRIGGER_PM11RcvInSel-SP', 'TRIGGER_PM11RcvInSel-RB', + 'TRIGGER_PM12RcvSrc-Sel', 'TRIGGER_PM12RcvSrc-Sts', + 'TRIGGER_PM12RcvInSel-SP', 'TRIGGER_PM12RcvInSel-RB', + 'TRIGGER_PM14RcvSrc-Sel', 'TRIGGER_PM14RcvSrc-Sts', + 'TRIGGER_PM14RcvInSel-SP', 'TRIGGER_PM14RcvInSel-RB', + 'ADCAD9510PllStatus-Mon']) + self._monitsum2intlksum_factor = 0 + for dev in self._fambpm_dev.devices: + pvo = dev.pv_object('ADCAD9510PllStatus-Mon') + pvo.auto_monitor = True + pvo.add_callback(self._callback_bpm_adclock) + + # # AFC physical trigger devices + phytrig_names = list() + for afcti, cratemap in self._const.crates_map.items(): + if '20RaBPMTL' in afcti: + continue + phytrig_names.extend(cratemap) + self._phytrig_devs = [ + _AFCPhysicalTrigger(dev, 4, props2init=[ + 'Dir-Sel', 'Dir-Sts', + 'DirPol-Sel', 'DirPol-Sts', + 'TrnLen-SP', 'TrnLen-RB']) + for dev in phytrig_names] + for dev in self._phytrig_devs: + pvo = dev.pv_object('Dir-Sel') + pvo.connection_callbacks.append(self._conn_callback_afcphystrigs) + + # # RF devices + self._llrf = _ASLLRF( + devname=_ASLLRF.DEVICES.SI, + props2init=[ + 'ILK:BEAM:TRIP:S', 'ILK:BEAM:TRIP', 'FASTINLK-MON', + 'ILK:MAN:S', 'ILK:MAN', 'IntlkSet-Cmd', 'Reset-Cmd', + ]) + self._llrf.pv_object('FASTINLK-MON').auto_monitor = True + + # # auxiliary devices + self._fofb = _FOFB( + props2init=['LoopState-Sts', ]) + self._sofb = _SOFB( + _SOFB.DEVICES.SI, + props2init=['LoopState-Sts', 'SlowSumRaw-Mon']) + self._sofb.pv_object('SlowSumRaw-Mon').auto_monitor = True + + # pvs to write methods + self.map_pv2write = { + 'Enable-Sel': self.set_enable, + 'PosEnblList-SP': _part(self.set_enbllist, 'pos'), + 'AngEnblList-SP': _part(self.set_enbllist, 'ang'), + 'MinSumEnblList-SP': _part(self.set_enbllist, 'minsum'), + 'PosXMinLim-SP': _part(self.set_intlk_lims, 'pos_x_min'), + 'PosXMaxLim-SP': _part(self.set_intlk_lims, 'pos_x_max'), + 'PosYMinLim-SP': _part(self.set_intlk_lims, 'pos_y_min'), + 'PosYMaxLim-SP': _part(self.set_intlk_lims, 'pos_y_max'), + 'AngXMinLim-SP': _part(self.set_intlk_lims, 'ang_x_min'), + 'AngXMaxLim-SP': _part(self.set_intlk_lims, 'ang_x_max'), + 'AngYMinLim-SP': _part(self.set_intlk_lims, 'ang_y_min'), + 'AngYMaxLim-SP': _part(self.set_intlk_lims, 'ang_y_max'), + 'MinSumLim-SP': _part(self.set_intlk_lims, 'minsum'), + 'ResetBPMGen-Cmd': _part(self.cmd_reset, 'bpm_gen'), + 'ResetBPMPos-Cmd': _part(self.cmd_reset, 'bpm_pos'), + 'ResetBPMAng-Cmd': _part(self.cmd_reset, 'bpm_ang'), + 'ResetBPM-Cmd': _part(self.cmd_reset, 'bpm_all'), + 'Reset-Cmd': _part(self.cmd_reset, 'all'), + 'PsMtmAcqChannel-Sel': self.set_acq_channel, + 'PsMtmAcqSamplesPre-SP': self.set_acq_nrspls_pre, + 'PsMtmAcqSamplesPost-SP': self.set_acq_nrspls_post, + 'PsMtmAcqConfig-Cmd': self.cmd_acq_config, + 'ResetTimingLockLatches-Cmd': self.cmd_reset_ti_lock_latch, + 'ResetAFCTimingRTMClk-Cmd': self.cmd_reset_afcti_rtmclk, + 'ConfigEVG-Cmd': self.cmd_config_evg, + 'ConfigFouts-Cmd': self.cmd_config_fouts, + 'ConfigAFCTiming-Cmd': self.cmd_config_afcti, + 'ConfigHLTriggers-Cmd': self.cmd_config_hltrigs, + 'ConfigLLRFIntlk-Cmd': self.cmd_config_llrf, + 'ConfigBPMs-Cmd': self.cmd_config_bpms, + 'ConfigAFCPhyTrigs-Cmd': self.cmd_config_phytrigs, + } + + # configuration scanning + self.thread_check_configs = _Repeat( + 1.0/App.SCAN_FREQUENCY, self._check_configs, niter=0, + is_cathread=True) + self.thread_check_configs.pause() + self.thread_check_configs.start() + + def init_database(self): + """Set initial PV values.""" + pvn2vals = { + 'Enable-Sel': self._state, + 'Enable-Sts': self._state, + 'BPMStatus-Mon': self._bpm_status, + 'TimingStatus-Mon': self._timing_status, + 'ResetBPMGen-Cmd': 0, + 'ResetBPMPos-Cmd': 0, + 'ResetBPMAng-Cmd': 0, + 'ResetBPM-Cmd': 0, + 'Reset-Cmd': 0, + 'PsMtmAcqChannel-Sel': self._acq_chan, + 'PsMtmAcqChannel-Sts': self._acq_chan, + 'PsMtmAcqSamplesPre-SP': self._acq_spre, + 'PsMtmAcqSamplesPre-RB': self._acq_spre, + 'PsMtmAcqSamplesPost-SP': self._acq_spost, + 'PsMtmAcqSamplesPost-RB': self._acq_spost, + 'PsMtmAcqConfig-Cmd': 0, + } + for pvn, val in pvn2vals.items(): + self.run_callbacks(pvn, val) + + # load autosave data + + # enable lists + for ilk in ['Pos', 'Ang', 'MinSum']: + ilkname = ilk.lower() + okl = self._load_file(ilkname, 'enbl') + pvn = f'{ilk}EnblList' + enb = self._enable_lists[ilkname] + self.run_callbacks(pvn+'-SP', enb) + if not okl: + self.run_callbacks(pvn+'-RB', enb) + self._bpm_mon_devs, self._ti_mon_devs = self._get_monitored_devices() + self.run_callbacks( + 'BPMMonitoredDevices-Mon', '\n'.join(self._bpm_mon_devs)) + self.run_callbacks( + 'TimingMonitoredDevices-Mon', '\n'.join(self._ti_mon_devs)) + self._config_fout_rxenbl() + + # limits + for ilk in ['Pos', 'Ang']: + for pln in ['X', 'Y']: + for lim in ['Min', 'Max']: + atn = f'{ilk}_{pln}_{lim}'.lower() + pvn = f'{ilk}{pln}{lim}Lim' + okl = self._load_file(atn, 'lim') + val = self._limits[atn] + self.run_callbacks(pvn+'-SP', val) + if not okl: + self.run_callbacks(pvn+'-RB', val) + + okl = self._load_file('minsum', 'lim') + val = self._limits['minsum'] + self.run_callbacks('MinSumLim-SP', val) + if not okl: + self.run_callbacks('MinSumLim-RB', val) + + # wait while enable list and limits setpoint queue to be empty + self._set_queue.join() + + self._update_log('Started.') + self._init = True + + # start init lock devices + self._enable_lock(init=True) + + def _enable_lock(self, init=False): + if not init: + self._lock_suspend = False + self._handle_lock_evg_configs(init) + self._handle_lock_fouts(init) + self._handle_lock_afcti(init) + self._handle_lock_hltriggers(init) + self._handle_lock_llrf(init) + self._handle_lock_bpm_configs(init) + self._handle_lock_afcphytrigs(init) + if init: + self._handle_lock_evg_enable(init) + self._handle_lock_bpm_enable(init) + + def _disable_lock(self): + self._lock_suspend = True + + def _handle_lock_evg_configs(self, init=False): + self._evg_dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for propty_sp, desired_val in self._const.EVG_CONFIGS: + propty_rb = _PVName.from_sp2rb(propty_sp) + pvo = self._evg_dev.pv_object(propty_rb) + if init: + pvo.add_callback(_part( + self._callback_lock, self._evg_dev, propty_sp, desired_val)) + else: + pvo.run_callbacks() + + def _handle_lock_evg_enable(self, init=False): + # lock interlock enable state + pvo = self._evg_dev.pv_object('IntlkCtrlEnbl-Sts') + if init: + pvo.add_callback(self._callback_evg_lock_intlk) + else: + pvo.run_callbacks() + + def _handle_lock_fouts(self, init=False): + for dev in self._fout_devs.values(): + dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + pvo = dev.pv_object('RxEnbl-RB') + if init: + pvo.add_callback(self._callback_fout_lock) + else: + pvo.run_callbacks() + + def _handle_lock_afcti(self, init=False): + for dev in self._afcti_devs.values(): + dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for propty_sp, desired_val in self._const.AFCTI_CONFIGS: + propty_rb = _PVName.from_sp2rb(propty_sp) + pvo = dev.pv_object(propty_rb) + if init: + pvo.add_callback(_part( + self._callback_lock, dev, propty_sp, desired_val)) + else: + pvo.run_callbacks() + + def _handle_lock_hltriggers(self, init=False): + for trigname, configs in self._const.HLTRIG_2_CONFIG: + trigdev = self._hltrig_devs[trigname] + trigdev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for prop_sp, desired_val in configs: + prop_rb = _PVName.from_sp2rb(prop_sp) + pvo = trigdev.pv_object(prop_rb) + if init: + pvo.add_callback( + _part(self._callback_lock, trigdev, prop_sp, desired_val)) + else: + pvo.run_callbacks() + + def _handle_lock_llrf(self, init=False): + self._llrf.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + pvo_beamtrip = self._llrf.pv_object('ILK:BEAM:TRIP') + pvo_manintlk = self._llrf.pv_object('ILK:MAN') + if init: + pvo_beamtrip.add_callback(_part( + self._callback_lock, self._llrf, + 'ILK:BEAM:TRIP:S', self._llrf_intlk_state)) + pvo_manintlk.add_callback(_part( + self._callback_lock, self._llrf, + 'ILK:MAN:S', self._llrf_intlk_state)) + else: + pvo_beamtrip.run_callbacks() + pvo_manintlk.run_callbacks() + + def _handle_lock_bpm_configs(self, init=False): + # lock BPM interlock enable and limits + prop2lock = [ + 'IntlkMinSumEn-Sts', + 'IntlkLmtMinSum-RB', + 'IntlkPosEn-Sts', + 'IntlkLmtPosMaxX-RB', + 'IntlkLmtPosMinX-RB', + 'IntlkLmtPosMaxY-RB', + 'IntlkLmtPosMinY-RB', + 'IntlkAngEn-Sts', + 'IntlkLmtAngMaxX-RB', + 'IntlkLmtAngMinX-RB', + 'IntlkLmtAngMaxY-RB', + 'IntlkLmtAngMinY-RB', + ] + self._orbintlk_dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for dev in self._orbintlk_dev.devices: + for prop in prop2lock: + pvo = dev.pv_object(prop) + if init: + pvo.add_callback(self._callback_bpm_lock) + else: + pvo.run_callbacks() + + # lock BPM logical triggers + self._fambpm_dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for dev in self._fambpm_dev.devices: + for prop_sp, desired_val in self._const.SIBPMLOGTRIG_CONFIGS: + prop_rb = _PVName.from_sp2rb(prop_sp) + pvo = dev.pv_object(prop_rb) + if init: + pvo.add_callback( + _part(self._callback_lock, dev, prop_sp, desired_val)) + else: + pvo.run_callbacks() + + def _handle_lock_bpm_enable(self, init=False): + self._orbintlk_dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for dev in self._orbintlk_dev.devices: + pvo = dev.pv_object('IntlkEn-Sts') + if init: + pvo.add_callback(self._callback_bpm_lock) + else: + pvo.run_callbacks() + + def _handle_lock_afcphytrigs(self, init=False): + for dev in self._phytrig_devs: + dev.wait_for_connection(timeout=self._const.DEF_TIMEOUT) + for prop_sp, desired_val in self._const.AFCPHYTRIG_CONFIGS: + # only lock polarity of other AFC physical triggers than SI BPM + if not self._check_lock_phytrig_prop(dev, prop_sp): + continue + prop_rb = _PVName.from_sp2rb(prop_sp) + pvo = dev.pv_object(prop_rb) + if init: + pvo.add_callback( + _part(self._callback_lock, dev, prop_sp, desired_val)) + else: + pvo.run_callbacks() + + @property + def pvs_database(self): + """Return pvs_database.""" + return self._pvs_database + + def process(self, interval): + """Sleep.""" + _time.sleep(interval) + + def read(self, reason): + """Read from IOC database.""" + _ = reason + return None + + def write(self, reason, value): + """Write value to reason and let callback update PV database.""" + _log.info('Write received for: %s --> %s', reason, str(value)) + if reason not in self.map_pv2write: + _log.warning('PV %s does not have a set function.', reason) + return False + + status = self.map_pv2write[reason](value) + _log.info( + '%s Write for: %s --> %s', str(status).upper(), reason, str(value)) + return status + + @property + def evg_dev(self): + """EVG device.""" + return self._evg_dev + + @property + def orbintlk_dev(self): + """Orbit interlock device.""" + return self._orbintlk_dev + + @property + def fambpm_dev(self): + """Return FamBPMs device.""" + return self._fambpm_dev + + # --- interlock control --- + + def set_enable(self, value): + """Set orbit interlock state. + Configure global BPM interlock enable and EVG interlock enable.""" + self._set_queue.put((self._do_set_enable, (value, ))) + return True + + def _do_set_enable(self, value): + if not 0 <= value < len(_ETypes.DSBL_ENBL): + self.run_callbacks('Enable-Sel', self._state) + return False + + if value: + if not self._check_ti_devices_status(self._ti_mon_devs): + self._update_log('ERR:Could not enable orbit interlock.') + self.run_callbacks('Enable-Sel', self._state) + return False + glob_en = self._get_gen_bpm_intlk() + else: + glob_en = _np.zeros(self._const.nr_bpms, dtype=bool) + + bkup = int(self._state) + self._state = value + + if self._state: + self._enable_lock() + else: + self._disable_lock() + + if not self._orbintlk_dev.set_gen_enable(list(glob_en)): + self._update_log('ERR:Could not set BPM general') + self._update_log('ERR:interlock enable.') + self._state = bkup + self.run_callbacks('Enable-Sel', self._state) + return False + self._update_log('Configured BPM general interlock enable.') + + self._evg_dev['IntlkCtrlEnbl-Sel'] = value + if not self._evg_dev._wait( + 'IntlkCtrlEnbl-Sts', value, timeout=self._const.DEF_TIMEOUT): + self._update_log('ERR:Could not set EVG interlock enable.') + self._state = bkup + self.run_callbacks('Enable-Sel', self._state) + return False + self._update_log('Configured EVG interlock enable.') + + self.run_callbacks('Enable-Sts', self._state) + + return True + + # --- enable lists --- + + def set_enbllist(self, intlk, value): + """Set enable list for interlock type.""" + if self._state: + self._update_log('ERR:Disable interlock before changing') + self._update_log('ERR:enable lists.') + return False + self._set_queue.put((self._do_set_enbllist, (intlk, value))) + return True + + def _do_set_enbllist(self, intlk, value): + intlkname = intlk.capitalize().replace('sum', 'Sum') + self._update_log(f'Setting {intlkname} EnblList...') + + # check size + new = _np.array(value, dtype=bool) + if self._const.nr_bpms != new.size: + self._update_log(f'ERR:Wrong {intlkname} EnblList size.') + self.run_callbacks( + f'{intlkname}EnblList-SP', self._enable_lists[intlk]) + return False + + # check coerence, down/up pair should have same enable state + if not self._check_valid_bpmconfig(new): + self._update_log('ERR:BPM should be enabled in pairs') + self._update_log('ERR:(M1/M2,C1-1/C1-2,C2/C3-1,C3-2/C4)') + self.run_callbacks( + f'{intlkname}EnblList-SP', self._enable_lists[intlk]) + return False + + bkup_enbllist = self._enable_lists[intlk] + self._enable_lists[intlk] = new + + # do not write to devices and save to file in initialization + if not self._init: + self._update_log('...done.') + # update readback pv + self.run_callbacks(f'{intlkname}EnblList-RB', new) + return True + + # check if new enable list do not imply in orbit interlock failure + if intlk in ['pos', 'ang']: + bkup_bpmmon, bkup_timon = self._bpm_mon_devs, self._ti_mon_devs + self._bpm_mon_devs, self._ti_mon_devs = \ + self._get_monitored_devices() + self._config_fout_rxenbl() + if not self._check_ti_devices_status(self._ti_mon_devs): + self._update_log('ERR:Could not set enable list.') + self._enable_lists[intlk] = bkup_enbllist + self._bpm_mon_devs, self._ti_mon_devs = bkup_bpmmon, bkup_timon + self._config_fout_rxenbl() + self.run_callbacks(f'{intlkname}EnblList-SP', bkup_enbllist) + return False + self.run_callbacks( + 'BPMMonitoredDevices-Mon', '\n'.join(self._bpm_mon_devs)) + self.run_callbacks( + 'TimingMonitoredDevices-Mon', '\n'.join(self._ti_mon_devs)) + + # handle device enable configuration + + # set BPM interlock specific enable state + fun = getattr(self._orbintlk_dev, f'set_{intlk}_enable') + ret = fun(list(value), timeout=3, return_prob=True) + if not ret[0]: + self._update_log(f'ERR:Could not set BPM {intlkname}') + self._update_log('ERR:interlock enable.') + for item in ret[1]: + self._update_log(f'ERR:Verify:{item}') + self.run_callbacks(f'{intlkname}EnblList-SP', bkup_enbllist) + return False + + # if interlock is already enabled, update BPM general enable state + if self._state and intlk in ['pos', 'ang']: + glob_en = self._get_gen_bpm_intlk() + ret = self._orbintlk_dev.set_gen_enable( + list(glob_en), timeout=3, return_prob=True) + if not ret[0]: + self._update_log('ERR:Could not set BPM general') + self._update_log('ERR:interlock enable.') + for item in ret[1]: + self._update_log(f'ERR:Verify:{item}') + self.run_callbacks(f'{intlkname}EnblList-SP', bkup_enbllist) + return False + + # save to autosave files + self._save_file(intlk, _np.array([value], dtype=bool), 'enbl') + + self._update_log('...done.') + + # update readback pv + self.run_callbacks(f'{intlkname}EnblList-RB', new) + return True + + # --- limits --- + + def set_intlk_lims(self, intlk_lim, value): + """Set limits for interlock type.""" + if self._state: + self._update_log('ERR:Disable interlock before changing') + self._update_log('ERR:interlock thresholds.') + return False + self._set_queue.put((self._do_set_intlk_lims, (intlk_lim, value))) + return True + + def _do_set_intlk_lims(self, intlk_lim, value): + parts = intlk_lim.split('_') + if len(parts) > 1: + ilk, pln, lim = parts + limname = f'{ilk.capitalize()}{pln.capitalize()}{lim.capitalize()}' + else: + limname = intlk_lim.capitalize().replace('sum', 'Sum') + self._update_log(f'Setting {limname} limits...') + + # check size + new = _np.array(value, dtype=int) + if self._const.nr_bpms != new.size: + self._update_log(f'ERR: Wrong {limname} limits size.') + self.run_callbacks(f'{limname}Lim-SP', self._limits[intlk_lim]) + return False + + # check coerence, down/up pair should have same limits + if not self._check_valid_bpmconfig(new): + self._update_log('ERR:BPM pairs should have equal limits') + self._update_log('ERR:(M1/M2,C1-1/C1-2,C2/C3-1,C3-2/C4)') + self.run_callbacks(f'{limname}Lim-SP', self._limits[intlk_lim]) + return False + + self._limits[intlk_lim] = new + + # do not set limits and save to file in initialization + if not self._init: + self._update_log('...done.') + # update readback pv + self.run_callbacks(f'{limname}Lim-RB', new) + return True + + # handle device limits configuration + + # set BPM interlock limits + fun = getattr(self._orbintlk_dev, f'set_{intlk_lim}_thres') + ret = fun(list(value), timeout=3, return_prob=True) + if not ret[0]: + self._update_log(f'ERR:Could not set BPM {limname}') + self._update_log('ERR:interlock limits.') + for item in ret[1]: + self._update_log(f'ERR:Verify:{item}') + self.run_callbacks(f'{limname}Lim-SP', self._limits[intlk_lim]) + return False + + # save to autosave files + self._save_file(intlk_lim, _np.array([value]), 'lim') + + self._update_log('...done.') + + # update readback pv + self.run_callbacks(f'{limname}Lim-RB', new) + return True + + # --- reset --- + + def cmd_reset(self, state, value=None): + """Reset interlock states.""" + _ = value + # if it is a BPM position, BPM general or a global reset + if 'pos' in state or 'all' in state: + self._orbintlk_dev.cmd_reset_pos() + self._update_log('Sent reset BPM position flags.') + # if it is a BPM angle, BPM general or a global reset + if 'ang' in state or 'all' in state: + self._orbintlk_dev.cmd_reset_ang() + self._update_log('Sent reset BPM angle flags.') + # if it is a BPM general or a global reset + if 'gen' in state or 'all' in state: + self._orbintlk_dev.cmd_reset_gen() + self._update_log('Sent reset BPM general flags.') + + # if it is a global reset, reset EVG + if state == 'all': + self._evg_dev['IntlkCtrlRst-Sel'] = 1 + self._update_log('Sent reset EVG interlock flag.') + + return True + + def cmd_reset_ti_lock_latch(self, value=None): + """Command to reset AFC timing and Fout clock lock latches.""" + _ = value + # try to reset AFC timing clock lock latches, act only in necessary + # devices, return false if fail + for idx, afcti in self._afcti_devs.items(): + if afcti['RTMClkLockedLtc-Mon']: + continue + afcti['ClkLockedLtcRst-Cmd'] = 1 + msg = 'Reset' if afcti._wait('RTMClkLockedLtc-Mon', 1, timeout=3) \ + else 'ERR:Could not reset' + self._update_log(f'{msg} AFC Timing {idx} lock latchs.') + if 'not' in msg: + return False + # try to reset BPM Fout rx lock latches, act only in necessary + # devices, return false if fail + for devname, fout in self._fout_devs.items(): + rxv = self._fout2rxenbl[devname] + if fout['RxLockedLtc-Mon'] == rxv: + continue + fout['RxLockedLtcRst-Cmd'] = 1 + msg = 'Reset' if fout._wait('RxLockedLtc-Mon', rxv, timeout=3) \ + else 'ERR:Could not reset' + self._update_log(f'{msg} {devname} lock latchs.') + if 'not' in msg: + return False + return True + + def cmd_reset_afcti_rtmclk(self, value=None): + """Command to reset AFC timing clocks.""" + _ = value + # do not allow user to reset in case of correction loops closed + if not self._fofb.connected or self._fofb['LoopState-Sts'] or \ + not self._sofb.connected or self._sofb['LoopState-Sts']: + self._update_log('ERR:Open correction loops before ') + self._update_log('ERR:reseting AFC Timing clocks.') + return False + + # try to reset AFC timing clock, act only in necessary + # devices, return false if fail + for idx, afcti in self._afcti_devs.items(): + if afcti['RTMClkLockedLtc-Mon']: + continue + afcti['ClkLockedLtcRst-Cmd'] = 1 + if afcti._wait('RTMClkLockedLtc-Mon', 1, timeout=3): + continue + afcti['RTMClkRst-Cmd'] = 1 + self._update_log(f'Sent reset clock to AFC Timing {idx}.') + + # try to reset latches + self.cmd_reset_ti_lock_latch() + + return True + + # --- BPM acquisition --- + + def set_acq_channel(self, value): + """Set BPM PsMtm acquisition channel.""" + self._acq_chan = value + self.run_callbacks('PsMtmAcqChannel-Sts', value) + return True + + def set_acq_nrspls_pre(self, value): + """Set BPM PsMtm acquisition number of samples pre.""" + self._acq_spre = value + self.run_callbacks('PsMtmAcqSamplesPre-RB', value) + return True + + def set_acq_nrspls_post(self, value): + """Set BPM PsMtm acquisition number of samples post.""" + self._acq_spost = value + self.run_callbacks('PsMtmAcqSamplesPost-RB', value) + return True + + def cmd_acq_config(self, value=None): + """Configure BPM PsMtm acquisition.""" + if self._thread_acq and self._thread_acq.is_alive(): + self._update_log('WARN:BPM configuration already in progress.') + return False + self._thread_acq = _CAThread(target=self._acq_config, daemon=True) + self._thread_acq.start() + return True + + def _acq_config(self): + self._update_log('Aborting BPM acquisition...') + ret = self._fambpm_dev.cmd_abort_mturn_acquisition() + if ret > 0: + self._update_log('ERR:Failed to abort BPM acquisition.') + return + self._update_log('...done. Configuring BPM acquisition...') + ret = self._fambpm_dev.config_mturn_acquisition( + nr_points_before=self._acq_spre, + nr_points_after=self._acq_spost, + acq_rate=self._acq_chan, + repeat=False, + external=True) + if ret < 0: + self._update_log( + 'ERR:Failed to abort acquisition for ' + + f'{self._const.bpm_names[-ret-1]:s}.') + return + if ret > 0: + self._update_log( + 'ERR:Failed to start acquisition for ' + + f'{self._const.bpm_names[ret-1]:s}.') + return + self._update_log('...done!') + + # --- devices configurations --- + + def cmd_config_evg(self, value): + """Configure EVG according to lock configurations.""" + _ = value + if not self._evg_dev.connected: + self._update_log('ERR:EVG disconnected.') + return False + for propty_sp, desired_val in self._const.EVG_CONFIGS: + propty_rb = _PVName.from_sp2rb(propty_sp) + if self._evg_dev[propty_rb] == desired_val: + continue + pvo = self._evg_dev.pv_object(propty_sp) + pvo.put(desired_val, wait=True) + _time.sleep(0.2) + return True + + def cmd_config_fouts(self, value): + """Configure Fouts according to lock configurations.""" + _ = value + for devname, dev in self._fout_devs.items(): + if not dev.connected: + self._update_log(f'ERR:{devname} disconnected.') + continue + desired_value = self._fout2rxenbl[devname] + dev['RxEnbl-SP'] = desired_value + dev._wait('RxEnbl-RB', desired_value, timeout=1) + dev['RxLockedLtcRst-Cmd'] = 1 + return True + + def cmd_config_afcti(self, value): + """Configure all AFC timing according to lock configurations.""" + _ = value + # do not allow user configurate loop params in case of + # correction loops closed + if not self._fofb.connected or self._fofb['LoopState-Sts'] or \ + not self._sofb.connected or self._sofb['LoopState-Sts']: + self._update_log('ERR:Open correction loops before ') + self._update_log('ERR:configuring AFC Timing RTM loop.') + return False + for dev in self._afcti_devs.values(): + if not dev.connected: + self._update_log(f'ERR:{dev.devname} disconnected.') + continue + for propty_sp, desired_val in self._const.AFCTI_CONFIGS: + dev[propty_sp] = desired_val + dev['ClkLockedLtcRst-Cmd'] = 1 + return True + + def cmd_config_hltrigs(self, value): + """Configure HL triggers according to lock configurations.""" + _ = value + for trigname, configs in self._const.HLTRIG_2_CONFIG: + trigdev = self._hltrig_devs[trigname] + if not trigdev.connected: + self._update_log(f'ERR:{trigname} disconnected.') + continue + for prop_sp, desired_val in configs: + trigdev[prop_sp] = desired_val + return True + + def cmd_config_llrf(self, value): + """Configure LLRF interlock according to lock configurations.""" + _ = value + if not self._llrf.connected: + self._update_log(f'ERR:LLRF disconnected.') + return False + self._llrf['ILK:BEAM:TRIP:S'] = self._llrf_intlk_state + self._llrf['ILK:MAN:S'] = self._llrf_intlk_state + return True + + def cmd_config_bpms(self, value): + """Configure BPMs according to lock configurations.""" + _ = value + if not self._orbintlk_dev.connected: + for dev in self._orbintlk_dev.devices: + self._update_log(f'ERR:{dev.devname} disconnected.') + return False + + for name, enbl in self._enable_lists.items(): + self.set_enbllist(name, enbl) + + for name, lim in self._limits.items(): + self.set_intlk_lims(name, lim) + + for dev in self._fambpm_dev.devices: + for prop, desired_val in self._const.SIBPMLOGTRIG_CONFIGS: + dev[prop] = desired_val + return True + + def cmd_config_phytrigs(self, value): + """Configure physical triggers according to lock configurations.""" + _ = value + for dev in self._phytrig_devs: + if not dev.connected: + self._update_log(f'ERR:{dev.devname} disconnected.') + continue + for prop, desired_val in self._const.AFCPHYTRIG_CONFIGS: + # only lock polarity of other AFC physical triggers than SI BPM + if not self._check_lock_phytrig_prop(dev, prop): + continue + dev[prop] = desired_val + return True + + # --- status methods --- + + def _config_fout_rxenbl(self): + fout2rx = dict() + for chn in self._ti_mon_devs: + if 'Fout' not in chn: + continue + fout = chn.device_name + outnam = chn.propty_name + if not outnam: + continue + out = int(outnam[-1]) + rx = fout2rx.get(fout, 0) + rx += 1 << out + fout2rx[fout] = rx + + for fout, dev in self._fout_devs.items(): + if fout in self._const.FOUTSFIXED_RXENBL: + continue + rxenbl = fout2rx.get(fout, 0) + self._fout2rxenbl[fout] = rxenbl + if not self._init: + continue + dev['RxEnbl-SP'] = rxenbl + dev._wait('RxEnbl-RB', rxenbl, timeout=1) + dev['RxLockedLtcRst-Cmd'] = 1 + if rxenbl: + dev._wait('RxLockedLtc-Mon', rxenbl, timeout=1) + + return True + + def _get_monitored_devices(self): + enbllist = self._get_gen_bpm_intlk() + aux = _np.roll(enbllist, 1) + subsecs = _np.where(_np.sum(aux.reshape(20, -1), axis=1) > 0)[0] + subsecs += 1 + + tidevs = set() + tidevs.add(self._evg_dev.devname) + bpmdevs = set() + for sub in subsecs: + # timing + afcti = f'IA-{sub:02}RaBPM:TI-AMCFPGAEVR' + tidevs.add(afcti) + foutout = self._const.trigsrc2fout_map[afcti] + tidevs.add(foutout) + fout = _PVName(foutout).device_name + tidevs.add(fout) + evgout = _LLTimeSearch.get_evg_channel(foutout) + tidevs.add(evgout) + if afcti in self._const.REDUNDANCY_TABLE: + afctir = self._const.REDUNDANCY_TABLE[afcti] + tidevs.add(afctir) + foutoutr = self._const.trigsrc2fout_map[afctir] + tidevs.add(foutoutr) + foutr = _PVName(foutoutr).device_name + tidevs.add(foutr) + evgoutr = _LLTimeSearch.get_evg_channel(foutoutr) + tidevs.add(evgoutr) + # bpm + bpmdevs.update(self._const.crates_map[afcti]) + + return sorted(bpmdevs), sorted(tidevs) + + def _check_ti_devices_status(self, devices): + for devname in devices: + devname = _PVName(devname) + + dev = self._evg_dev if 'EVG' in devname else \ + self._fout_devs[devname.device_name] if 'Fout' in devname \ + else self._afcti_devs[int(devname.sub[:2])] \ + if 'AMCFPGA' in devname else None + if dev is None: + return True + + if not dev.connected: + self._update_log(f'ERR:{dev.devname} not connected') + return False + elif 'Fout' in devname: + out = int(devname.propty[-1]) if devname.propty else None + if out is not None and not _get_bit(dev['RxLockedLtc-Mon'], out): + self._update_log(f'ERR:{dev.devname} OUT{out} not locked') + return False + elif 'AMCFPGA' in devname and not dev['RTMClkLockedLtc-Mon']: + self._update_log(f'ERR:{dev.devname} RTM Clk not locked') + return False + return True + + def _get_gen_bpm_intlk(self): + pos, ang = self._enable_lists['pos'], self._enable_lists['ang'] + return _np.logical_or(pos, ang) + + def _check_valid_bpmconfig(self, config): + aux = _np.roll(config, 1) + # check if pairs have the same config + return not _np.any(_np.diff(aux.reshape(-1, 2), axis=1) != 0) + + def _check_lock_phytrig_prop(self, dev, prop): + devname = _PVName(dev.devname).device_name + if devname in self._const.bpm_names: + return True + return prop == 'DirPol-Sel' + + def _check_configs(self): + _t0 = _time.time() + + # bpm status + value = 0 + if self._orbintlk_dev.connected and self._fambpm_dev.connected: + dev = self._orbintlk_dev + # PosEnblSynced + val = _np.array_equal( + dev.pos_enable, self._enable_lists['pos']) + value = _updt_bit(value, 1, not val) + # AngEnblSynced + val = _np.array_equal( + dev.ang_enable, self._enable_lists['ang']) + value = _updt_bit(value, 2, not val) + # MinSumEnblSynced + val = _np.array_equal( + dev.minsum_enable, self._enable_lists['minsum']) + value = _updt_bit(value, 3, not val) + # GlobEnblSynced + genval = self._get_gen_bpm_intlk() if self._state else \ + _np.zeros(self._const.nr_bpms, dtype=bool) + val = _np.array_equal(dev.gen_enable, genval) + value = _updt_bit(value, 4, not val) + # PosLimsSynced + okp = True + for prp in ['pos_x_min', 'pos_x_max', 'pos_y_min', 'pos_y_max']: + okp &= _np.array_equal( + getattr(dev, prp+'_thres'), self._limits[prp]) + value = _updt_bit(value, 5, not okp) + # AngLimsSynced + oka = True + for prp in ['ang_x_min', 'ang_x_max', 'ang_y_min', 'ang_y_max']: + oka &= _np.array_equal( + getattr(dev, prp+'_thres'), self._limits[prp]) + value = _updt_bit(value, 6, not oka) + # MinSumLimsSynced + oks = _np.array_equal(dev.minsum_thres, self._limits['minsum']) + value = _updt_bit(value, 7, not oks) + # AcqConfigured + bpms = self._fambpm_dev.devices + okb = all(d.acq_channel == self._acq_chan for d in bpms) + okb &= all([d.acq_nrsamples_post == self._acq_spost for d in bpms]) + okb &= all([d.acq_nrsamples_pre == self._acq_spre for d in bpms]) + okb &= all( + d.acq_repeat == self._const.AcqRepeat.Normal for d in bpms) + okb &= all( + d.acq_trigger == self._const.AcqTrigTyp.External for d in bpms) + okb &= all( + d.acq_status == self._const.AcqStates.External_Trig for d in bpms) + value = _updt_bit(value, 8, not okb) + # LogTrigConfigured + okl = True + for bpm in self._fambpm_dev.devices: + for prp, val in self._const.SIBPMLOGTRIG_CONFIGS: + prp_rb = _PVName.from_sp2rb(prp) + okl &= bpm[prp_rb] == val + value = _updt_bit(value, 9, not okl) + + self._bpm_status = value + self.run_callbacks('BPMStatus-Mon', self._bpm_status) + + # Timing Status + value = 0 + # EVG + dev = self._evg_dev + if dev.connected: + val = dev['IntlkCtrlEnbl-Sts'] != self._state + value = _updt_bit(value, 1, val) + okg = True + for prp, val in self._const.EVG_CONFIGS: + prp_rb = _PVName.from_sp2rb(prp) + okg &= dev[prp_rb] == val + value = _updt_bit(value, 2, not okg) + else: + value = 0b111 + # Fouts + if all(dev.connected for dev in self._fout_devs.values()): + okg = True + for devname, rxenbl in self._fout2rxenbl.items(): + dev = self._fout_devs[devname] + okg &= dev['RxEnbl-RB'] == rxenbl + okg &= dev['RxLockedLtc-Mon'] == rxenbl + value = _updt_bit(value, 4, not okg) + else: + value += 0b11 << 3 + # AFC timing + if all(dev.connected for dev in self._afcti_devs.values()): + okg = True + for dev in self._afcti_devs.values(): + for prp, val in self._const.AFCTI_CONFIGS: + prp_rb = _PVName.from_sp2rb(prp) + okg &= dev[prp_rb] == val + value = _updt_bit(value, 6, not okg) + else: + value += 0b11 << 5 + # AFC Physical triggers + if all(dev.connected for dev in self._phytrig_devs): + okg = True + for dev in self._phytrig_devs: + for prp, val in self._const.AFCPHYTRIG_CONFIGS: + if not self._check_lock_phytrig_prop(dev, prp): + continue + prp_rb = _PVName.from_sp2rb(prp) + okg &= dev[prp_rb] == val + value = _updt_bit(value, 8, not okg) + else: + value += 0b11 << 7 + # HL triggers + bit = 9 + for trigname, configs in self._const.HLTRIG_2_CONFIG: + dev = self._hltrig_devs[trigname] + if dev.connected: + value = _updt_bit(value, bit+1, bool(dev['Status-Mon'])) + oko = True + for prp, val in configs: + prp_rb = _PVName.from_sp2rb(prp) + oko &= dev[prp_rb] == val + value = _updt_bit(value, bit+2, not oko) + else: + value += 0b111 << bit + bit += 3 + + self._timing_status = value + self.run_callbacks('TimingStatus-Mon', self._timing_status) + + # LLRF Status + value = (1 << 2) - 1 + dev = self._llrf + if dev.connected: + value = _updt_bit(value, 0, 0) + okc = dev['ILK:BEAM:TRIP'] == self._llrf_intlk_state + okc &= dev['ILK:MAN'] == self._llrf_intlk_state + value = _updt_bit(value, 1, not okc) + self.run_callbacks('LLRFStatus-Mon', value) + + # check time elapsed + ttook = _time.time() - _t0 + tplanned = self.thread_check_configs.interval + tsleep = tplanned - ttook + if tsleep <= 0: + _log.warning( + 'Configuration check took more than planned... ' + '{0:.3f}/{1:.3f} s'.format(ttook, tplanned)) + + # --- interlock methods --- + + def _callback_evg_intlk(self, value, **kws): + _ = kws + if not self._state: + return + if self._thread_cbevgilk and self._thread_cbevgilk.is_alive(): + return + self._thread_cbevgilk = _CAThread( + target=self._do_callback_evg_intlk, args=(value, ), daemon=True) + self._thread_cbevgilk.start() + + def _do_callback_evg_intlk(self, value): + if value == 0: + return + + self._update_log('FATAL:Orbit interlock raised by EVG.') + + self._update_log('Waiting a little before rearming...') + _time.sleep(self._const.DEF_TIME2WAIT_INTLKREARM) + + # reset latch flags for BPM interlock core and EVG + self.cmd_reset('all') + + # reconfigure BPM configuration + self.cmd_acq_config() + + def _callback_fout_rxlock(self, pvname, value, **kws): + if not self._init: + return + nam = _PVName(pvname).device_name + if self._thread_cbfout[nam] and self._thread_cbfout[nam].is_alive(): + return + self._thread_cbfout[nam] = _CAThread( + target=self._do_callback_rxlock, + args=(pvname, value, ), daemon=True) + self._thread_cbfout[nam].start() + + def _callback_evg_rxlock(self, pvname, value, **kws): + if not self._init: + return + pvname = _PVName(pvname) + configs = self._const.EVG_CONFIGS + bits = [int(c[0][-1]) for c in configs if 'RxEnbl' in c[0]] + if all([_get_bit(value, b) for b in bits]): # all ok + return + if self._thread_cbevgrx and self._thread_cbevgrx.is_alive(): + return + self._thread_cbevgrx = _CAThread( + target=self._do_callback_rxlock, + args=(pvname, value, ), daemon=True) + self._thread_cbevgrx.start() + + def _do_callback_rxlock(self, pvname, value): + pvname = _PVName(pvname) + devname = pvname.device_name + if pvname.dev == 'EVG': + is_failure = False + for bit in range(8): + if _get_bit(value, bit): + continue + outnam = f'OUT{bit}' + self._update_log(f'WARN:{outnam} of {devname} not locked') + devout = devname.substitute(propty_name=outnam) + # verify if this is an orbit interlock reliability failure + is_failure |= devout in self._ti_mon_devs + else: + outs_in_failure = set() + for dev in self._ti_mon_devs: + # verify fouts + if 'Fout' not in dev: + continue + dev = _PVName(dev) + # if the fout from callback is a monitored one + if dev.device_name == devname: + # verify if the monitored outs are locked + outnam = dev.propty_name + if not outnam: + continue + out = int(outnam[-1]) + if _get_bit(value, out): + continue + # if not, it is a reliability failure + outs_in_failure.add(out) + self._update_log(f'WARN:{outnam} of {devname} not locked') + # verify redundancy pairs, failure only if both are in failure + aux_var_loop = outs_in_failure.copy() + for out in aux_var_loop: + outnam = f'OUT{out}' + devout = devname.substitute(propty_name=outnam) + if devout in self._const.intlkr_fouttable: + pair = self._const.intlkr_fouttable[devout] + devpair = _PVName(pair).device_name + if self._fout_devs[devpair]['RxLockedLtc-Mon']: + outs_in_failure.remove(out) + else: + self._update_log(f'WARN:{outnam} of {pair} not locked') + is_failure = bool(outs_in_failure) + + if not is_failure: + return + self._handle_reliability_failure() + + def _conn_callback_timing(self, pvname, conn, **kws): + if conn: + return + devname = _PVName(pvname).device_name + # verify if this is an orbit interlock reliability failure + is_failure = devname in self._ti_mon_devs + flag = 'FATAL' if is_failure else 'WARN' + self._update_log(f'{flag}:{devname} disconnected') + if is_failure: + self._handle_reliability_failure() + + def _callback_bpm_intlk(self, pvname, value, **kws): + _ = kws + if not value: + return + # launch thread to log interlock details + bpmname = _PVName(pvname).device_name + _CAThread( + target=self._log_bpm_intlk, + args=(bpmname, ), + daemon=True).start() + # launch thread to send interlock to RF as a backup + if self._thread_cbbpm and self._thread_cbbpm.is_alive(): + return + + # NOTE: the next lines help to avoid killing beam in case one BPM that + # is not enabled raises a false positive interlock signal. + idx = self._const.bpm_idcs[bpmname] + enbl = self._enable_lists['pos'][idx] or self._enable_lists['ang'][idx] + if not enbl: + self._update_log(f'WARN:{bpmname} false positive') + return + + self._thread_cbbpm = _CAThread( + target=self._do_callback_bpm_intlk, daemon=True) + self._thread_cbbpm.start() + + def _log_bpm_intlk(self, bpmname): + # log which interlock flag was raised + self._update_log(f'FATAL:{bpmname} raised interlock.') + props = [ + 'IntlkPosLowerLtcX-Mon', 'IntlkPosUpperLtcX-Mon', + 'IntlkPosLowerLtcY-Mon', 'IntlkPosUpperLtcY-Mon', + 'IntlkAngLowerLtcX-Mon', 'IntlkAngUpperLtcX-Mon', + 'IntlkAngLowerLtcY-Mon', 'IntlkAngUpperLtcY-Mon', + ] + for prop in props: + idx = self._const.bpm_names.index(bpmname) + intlk, pln = prop.split('-')[0].split('Intlk')[1].split('Ltc') + if self._orbintlk_dev.devices[idx][prop]: + self._update_log(f'FATAL:{bpmname} > {intlk} {pln}') + + def _do_callback_bpm_intlk(self): + # send kill beam as fast as possible + self._handle_reliability_failure(is_failure=False) + # wait minimum period for RF EVE event count to be updated + _time.sleep(.1) + # verify if RF EVE counted the event PsMtm + new_evtcnt = self._everf_dev[self._llrf_evtcnt_pvname] + if new_evtcnt == self._everf_evtcnt: + self._update_log('WARN:RF EVE did not count event PsMtm') + self._everf_evtcnt = new_evtcnt + # wait minimum period for BPM to update interlock PVs + _time.sleep(2) + # verify if EVG propagated the event Intlk + evgintlksts = self._evg_dev['IntlkEvtStatus-Mon'] + if not evgintlksts & 0b1: + self._update_log('ERR:EVG did not propagate event Intlk') + # reset BPM orbit interlock, once EVG callback was not triggered + self.cmd_reset('bpm_all') + if not self._llrf['FASTINLK-MON'] & (1 << 12): + self._update_log('ERR:LLRF did not received RFKill event') + + def _get_bpm_rates_factor(self): + if self._monitsum2intlksum_factor: + return self._monitsum2intlksum_factor + monit = self._fambpm_dev.devices[0]['INFOMONITRate-RB'] + facq = self._fambpm_dev.devices[0]['INFOFAcqRate-RB'] + + if None in [monit, facq]: + return 0 + frac = monit/facq + factor = 2**_np.ceil(_np.log2(frac)) / frac + self._monitsum2intlksum_factor = factor + return self._monitsum2intlksum_factor + + def _callback_bpm_adclock(self, pvname, value, **kws): + _ = kws + if value == 1: + return + devname = _PVName(pvname).device_name + is_failure = devname in self._bpm_mon_devs and \ + devname in self._const.bpm_names + flag = 'FATAL' if is_failure else 'WARN' + self._update_log(f'{flag}:{devname} lost PLL lock') + if is_failure: + self._handle_reliability_failure() + + def _conn_callback_afcphystrigs(self, pvname, conn, **kws): + _ = kws + if conn: + return + devname = _PVName(pvname).device_name + is_failure = devname in self._bpm_mon_devs + flag = 'ERR' if is_failure else 'WARN' + self._update_log(f'{flag}:{devname} disconnected') + if is_failure: + self._handle_reliability_failure() + + def _callback_hltrig_status(self, pvname, value, **kws): + _ = kws + if not value: + return + # if status is not ok, it is a reliability failure + trigname = _PVName(pvname).device_name + self._update_log(f'FATAL:{trigname} Status not ok') + self._handle_reliability_failure() + + # --- reliability failure methods --- + + def _check_minsum_requirement(self, monit_sum=None): + if monit_sum is None: + monit_sum = self._sofb['SlowSumRaw-Mon'] + facq_sum = monit_sum * self._get_bpm_rates_factor() + return _np.all(facq_sum > self._limits['minsum']) + + def _handle_reliability_failure(self, is_failure=True): + if is_failure: + flag = 'FATAL' if self._state else 'WARN' + self._update_log(f'{flag}:Orbit interlock reliability failure') + if not self._state: + self._update_log('WARN:Orbit interlock is not enabled.') + return + # send soft interlock to RF + self._update_log('FATAL:sending soft interlock to LLRF.') + self._llrf['IntlkSet-Cmd'] = 1 + _time.sleep(1) + self._llrf['IntlkSet-Cmd'] = 0 + if self._is_dry_run: + # wait a little and rearming FDL acquisition + _time.sleep(self._const.DEF_TIME2WAIT_INTLKREARM) + self._llrf['Reset-Cmd'] = 1 + _time.sleep(1) + self._llrf['Reset-Cmd'] = 0 + + # --- device lock methods --- + + def _callback_lock( + self, device, propty_sp, desired_value, pvname, value, **kwargs): + thread = _CAThread( + target=self._start_lock_thread, + args=(device, propty_sp, desired_value, pvname, value), + daemon=True) + thread.start() + + def _callback_evg_lock_intlk(self, pvname, value, **kwargs): + thread = _CAThread( + target=self._start_lock_thread, + args=( + self._evg_dev, 'IntlkCtrlEnbl-Sel', self._state, + pvname, value), + daemon=True) + thread.start() + + def _callback_fout_lock(self, pvname, value, **kwargs): + devname = _PVName(pvname).device_name + desired_value = self._fout2rxenbl[devname] + device = self._fout_devs[devname] + thread = _CAThread( + target=self._start_lock_thread, + args=(device, 'RxEnbl-SP', desired_value, pvname, value), + daemon=True) + thread.start() + + def _callback_bpm_lock(self, pvname, value, **kws): + pvname = _PVName(pvname) + devname = pvname.device_name + propty_rb = pvname.propty + propty_sp = _PVName.from_rb2sp(propty_rb) + devidx = self._orbintlk_dev.BPM_NAMES.index(devname) + device = self._orbintlk_dev.devices[devidx] + if propty_rb.endswith('En-Sts'): + entyp = 'pos' if 'Pos' in propty_rb else \ + 'ang' if 'Ang' in propty_rb else \ + 'minsum' if 'MinSum' in propty_rb else \ + 'gen' + if entyp == 'gen': + desired_value = self._get_gen_bpm_intlk()[devidx] \ + if self._state else 0 + else: + desired_value = self._enable_lists[entyp][devidx] + elif 'Lmt' in propty_rb: + limcls = 'pos' if 'Pos' in propty_rb else \ + 'ang' if 'Ang' in propty_rb else 'minsum' + limpln = '_x_' if 'X' in propty_rb else \ + '_y_' if 'Y' in propty_rb else '' + limtyp = '' if 'MinSum' in propty_rb \ + else 'max' if 'Max' in propty_rb else 'min' + limname = f'{limcls}{limpln}{limtyp}' + desired_value = self._limits[limname][devidx] + + thread = _CAThread( + target=self._start_lock_thread, + args=(device, propty_sp, desired_value, pvname, value), + daemon=True) + thread.start() + + def _start_lock_thread( + self, device, propty_sp, desired_value, pvname, value): + if self._lock_suspend: + return + + # do not try to lock devices that are not in list of monitored devices + devname = _PVName(pvname).device_name + if devname not in self._ti_mon_devs and \ + devname not in self._bpm_mon_devs and \ + devname not in self._hltrig_devs: + return + + # if there is already a lock thread, return + thread = self._lock_threads.get(pvname, None) + if thread is not None and thread.is_alive(): + return + + # else, create lock thread with 10 attempts to lock PV + interval = 1 / 10 # little sleep to avoid CPU load + thread = _Repeat( + interval, self._do_lock, + args=(device, propty_sp, desired_value, pvname, value), + niter=10, is_cathread=True) + self._lock_threads[pvname] = thread + thread.start() + + def _do_lock(self, device, propty_sp, desired_value, pvname, value): + thread = self._lock_threads[pvname] + + # if value is equal desired, stop thread + if value == desired_value: + if pvname in self._lock_failures: + self._lock_failures.remove(pvname) + thread.stop() + return + + # else, apply value as desired + if device == self._llrf: + propty_rb = propty_sp.replace(':S', '') + else: + propty_rb = _PVName.from_sp2rb(propty_sp) + self._update_log(f'WARN:Locking {pvname}') + device[propty_sp] = desired_value + + # if readback reached desired value, stop thread + if device._wait(propty_rb, desired_value, timeout=0.11): + if pvname in self._lock_failures: + self._lock_failures.remove(pvname) + thread.stop() + return + + # if this was the last iteration, raise a reliability failure + if thread.cur_iter == thread.niters-1: + self._lock_failures.add(pvname) + self._update_log(f'FATAL:Fail to lock {pvname}') + self._handle_reliability_failure() + + # --- auxiliary log 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) + + # --- file handlers --- + + def _load_file(self, intlk, dtype='en'): + filename, desc = self._get_file_info(intlk, dtype) + if not _os.path.isfile(filename): + return + value = _np.loadtxt(filename) + okl = True + if dtype.startswith('en'): + okl = self.set_enbllist(intlk, value) + elif dtype.startswith('lim'): + okl = self.set_intlk_lims(intlk, value) + if okl: + msg = f'Loaded {intlk} {desc} from auto save!' + else: + msg = f'ERR:Problem loading {intlk} {desc} from file.' + self._update_log(msg) + return okl + + def _save_file(self, intlk, value, dtype): + filename, desc = self._get_file_info(intlk, dtype) + try: + path = _os.path.split(filename)[0] + _os.makedirs(path, exist_ok=True) + _np.savetxt(filename, value) + except FileNotFoundError: + self._update_log( + f'WARN:Could not save {intlk} {desc} to file.') + + def _get_file_info(self, intlk, dtype): + if dtype.startswith(('en', 'lim')): + desc = 'enable list' if dtype.startswith('en') else 'limits' + suff = '_enbl' if dtype.startswith('en') else '_lim' + fname = intlk + suff + else: + raise ValueError(f'file info not defined for {intlk} and {dtype}') + filename = getattr(self._const, fname + '_fname') + return filename, desc diff --git a/siriuspy/siriuspy/search/id_search.py b/siriuspy/siriuspy/search/id_search.py index 032dc193d..9dd00df9e 100644 --- a/siriuspy/siriuspy/search/id_search.py +++ b/siriuspy/siriuspy/search/id_search.py @@ -44,6 +44,30 @@ class IDSearch: ) _idname2params = { + 'SI-06SB:ID-APU22': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 22, + 0, 11, 11, 0, 0.01, + None, None, None, None)), + 'SI-07SP:ID-APU22': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 22, + 0, 11, 11, 0, 0.01, + None, None, None, None)), + 'SI-08SB:ID-APU22': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 22, + 0, 11, 11, 0, 0.01, + None, None, None, None)), + 'SI-09SA:ID-APU22': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 22, + 0, 11, 11, 0, 0.01, + None, None, None, None)), # NOTE: for EPU50 there is a large discrepancy # between RB/SP/Mon phase values 'SI-10SB:ID-EPU50': _get_namedtuple( @@ -56,14 +80,44 @@ class IDSearch: 'IDParameters', _idparam_fields, ( 52.5, - -52.5/2, +52.5/2, 0, 0, 0.1, - -52.5/2, +52.5/2, 0, 0.1)), + -52.5/2, +52.5/2, 0, 0, 0.020, + -52.5/2, +52.5/2, 0, 0.010)), + 'SI-11SP:ID-APU58': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 58, + 0, 29, 29, 0, 0.01, + None, None, None, None)), + 'SI-14SB:ID-WIG180': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 180, + 49.73, 49.73, 150, 150, 0.1, + None, None, None, None)), + 'SI-17SA:ID-PAPU50': _get_namedtuple( + 'IDParameters', + _idparam_fields, ( + 50, + 0, 25, 25, 0, 0.1, + None, None, None, None)), } POL_NONE_STR = 'none' POL_UNDEF_STR = 'undef' _idname2pol_sel = { + 'SI-06SB:ID-APU22': { + 0: ('horizontal', None), # [mm] + }, + 'SI-07SP:ID-APU22': { + 0: ('horizontal', None), # [mm] + }, + 'SI-08SB:ID-APU22': { + 0: ('horizontal', None), # [mm] + }, + 'SI-09SA:ID-APU22': { + 0: ('horizontal', None), # [mm] + }, 'SI-10SB:ID-EPU50': { 0: ('circularn', -16.36), # [mm] 1: ('horizontal', 0.00), # [mm] @@ -71,10 +125,16 @@ class IDSearch: 3: ('vertical', 25.00), # [mm] }, 'SI-10SB:ID-DELTA52': { - 0: ('vertical', -52.5/2), # [mm] 0: ('circularn', -52.5/4), # [mm] 1: ('horizontal', 0.00), # [mm] 2: ('circularp', +52.5/4), # [mm] + 3: ('vertical', -52.5/2), # [mm] + }, + 'SI-11SP:ID-APU58': { + 0: ('horizontal', None), # [mm] + }, + 'SI-17SA:ID-PAPU50': { + 0: ('horizontal', None), # [mm] }, } _idname2pol_sts = _copy.deepcopy(_idname2pol_sel) @@ -105,8 +165,8 @@ class IDSearch: 'polarizations': tuple( item[0] for item in _idname2pol_sts['SI-10SB:ID-DELTA52'].values()), - 'pparameter': 'SI-10SB:ID-DELTA52:PolShift-Mon', - 'kparameter': 'SI-10SB:ID-DELTA52:GainShift-Mon', + 'pparameter': 'SI-10SB:ID-DELTA52:PParam-Mon', + 'kparameter': 'SI-10SB:ID-DELTA52:KParam-Mon', 'ch1': 'SI-10SB:PS-CH-1:Current-SP', # upstream 'ch2': 'SI-10SB:PS-CH-2:Current-SP', # downstream 'cv1': 'SI-10SB:PS-CV-1:Current-SP', @@ -136,7 +196,7 @@ class IDSearch: @staticmethod def get_idnames(filters=None): """Return a sorted and filtered list of all ID names.""" - idnames_list = list(IDSearch._idname2beamline.keys()) + idnames_list = list(IDSearch._idname_2_idff.keys()) idnames = _Filter.process_filters(idnames_list, filters=filters) return sorted(idnames) @@ -229,13 +289,13 @@ def conv_idname_2_idff_qsnames(idname): def conv_idname_2_polarizations(idname): """Return ID polarizations (sel).""" pols = IDSearch._idname2pol_sel[idname] - return tuple(pol[0] for pol in pols) + return tuple(pol[0] for pol in pols.values()) @staticmethod def conv_idname_2_polarizations_sts(idname): """Return ID polarizations (sts).""" pols = IDSearch._idname2pol_sts[idname] - return tuple(pol[0] for pol in pols) + return tuple(pol[0] for pol in pols.values()) @staticmethod def conv_idname_2_polarization_state(idname, pparameter, kparameter): @@ -244,8 +304,10 @@ def conv_idname_2_polarization_state(idname, pparameter, kparameter): pols_sts = IDSearch._idname2pol_sts[idname] # check if polarization is defined - for pol_idx, pol in pols_sts: + for pol_idx, pol in pols_sts.items(): _, pol_phase = pol + if pol_phase is None: # ignore none and undef configs + continue if abs(pparameter - pol_phase) <= params.PPARAM_TOL: return pol_idx @@ -267,7 +329,7 @@ def conv_idname_2_polarization_pparameter(idname, pol): if isinstance(pol, int): return pols[pol][1] elif isinstance(pol, str): - for _, (pol_name, pol_pparam) in pols: + for pol_name, pol_pparam in pols.values(): if pol == pol_name: return pol_pparam raise ValueError(f'Invalid polarization string "{pol}"') diff --git a/siriuspy/siriuspy/search/ll_time_search.py b/siriuspy/siriuspy/search/ll_time_search.py index eeae9cb38..a878dfa0b 100644 --- a/siriuspy/siriuspy/search/ll_time_search.py +++ b/siriuspy/siriuspy/search/ll_time_search.py @@ -360,7 +360,11 @@ def has_log(cls, ll_trigger): @classmethod def get_trigger_name(cls, channel): """Get name of the trigger associated with channel.""" - chan_tree = cls.get_device_tree(channel) + # Look for trigger source from EVG down to channel. + # This is important to handle cases where 2 trigger sources are on the + # same leaf of the tree, such as the redundancy trigger for the orbit + # interlock. + chan_tree = cls.get_device_tree(channel)[::-1] for up_chan in chan_tree: if up_chan.device_name in cls._trig_src_devs: return up_chan @@ -477,7 +481,7 @@ def _get_crates_mapping(cls): line = line.strip() if not line or line[0] == '#': continue # empty line - crate, dev, *_ = line.split() + dev, *_, crate = line.split() dev = _PVName(dev) if crate not in mapping and dev.dev == 'AMCFPGAEVR': crates[crate] = dev diff --git a/siriuspy/siriuspy/simul/simps.py b/siriuspy/siriuspy/simul/simps.py index 69de78171..928470345 100644 --- a/siriuspy/siriuspy/simul/simps.py +++ b/siriuspy/siriuspy/simul/simps.py @@ -75,7 +75,7 @@ def get_pwrstate(self, pvname): if pvn not in self: # If PwrState-Sts not simulated, assume it to be On. return _Const.PwrStateSts.On - return self.pv_value_get(pvn) + return self.get_pv_value(pvn) @staticmethod def conv_psname2typemodel(psname): @@ -136,7 +136,7 @@ def callback_update(self, **kwargs): else: setpoints = dict() for sp_pvname in self._setpoint_pvs: - setpoints[sp_pvname] = self.pv_value_get(sp_pvname) + setpoints[sp_pvname] = self.get_pv_value(sp_pvname) # synchronize Current PVs for sp_pvname, sp_value in setpoints.items(): diff --git a/siriuspy/siriuspy/simul/simulator.py b/siriuspy/siriuspy/simul/simulator.py index c598a15ce..ca99b1ac5 100644 --- a/siriuspy/siriuspy/simul/simulator.py +++ b/siriuspy/siriuspy/simul/simulator.py @@ -98,7 +98,7 @@ def values(self): """Return dict with pvnames and associated values of simulator.""" vals = dict() for pvname in self._pvs: - vals[pvname] = self.pv_value_get(pvname) + vals[pvname] = self.get_pv_value(pvname) return vals def pv_check(self, pvname): @@ -109,7 +109,7 @@ def pv_check(self, pvname): return True return False - def pv_value_get(self, pvname): + def get_pv_value(self, pvname): """Get SimPV value without invoking simulator callback.""" return self._pvs[pvname].get_sim() diff --git a/siriuspy/siriuspy/thread.py b/siriuspy/siriuspy/thread.py index ad8602f42..ac29f76db 100644 --- a/siriuspy/siriuspy/thread.py +++ b/siriuspy/siriuspy/thread.py @@ -102,6 +102,8 @@ def run(self): _use_initial_context() self._unpaused.wait() + if self._stopped.is_set(): + return self.function(*self.args, **self.kwargs) dtime = 0.0 while ((not self._stopped.wait(self.interval - dtime)) and @@ -140,8 +142,8 @@ def isPaused(self): def stop(self): """Stop execution.""" - self._unpaused.set() self._stopped.set() + self._unpaused.set() class _BaseQueueThread(_Queue): diff --git a/siriuspy/siriuspy/timesys/csdev.py b/siriuspy/siriuspy/timesys/csdev.py index 6e511514d..f33dde0e9 100644 --- a/siriuspy/siriuspy/timesys/csdev.py +++ b/siriuspy/siriuspy/timesys/csdev.py @@ -67,12 +67,7 @@ class Const(_csdev.Const): __EvtHL2LLMap = None __EvtLL2HLMap = None - - evt_ll_codes = list(range(64)) + [117, 124, 125, 126, 132] - evt_ll_names = ['Evt{0:02d}'.format(i) for i in evt_ll_codes] - EvtLL = _csdev.Const.register( - 'EventsLL', evt_ll_names, values=evt_ll_codes) - del evt_ll_codes, evt_ll_names # cleanup class namespace + __EvtLL = None ClkHL2LLMap = { 'Clock0': 'Clk0', 'Clock1': 'Clk1', @@ -90,10 +85,17 @@ class Const(_csdev.Const): @_classproperty def EvtHL2LLMap(cls): """.""" - if cls.__EvtHL2LLMap is None: - cls.__EvtHL2LLMap = _HLTimeSearch.get_hl_events() - cls.__EvtLL2HLMap = { - val: key for key, val in cls.__EvtHL2LLMap.items()} + if cls.__EvtHL2LLMap is not None: + return cls.__EvtHL2LLMap + + emap = _HLTimeSearch.get_hl_events() + cls.__EvtHL2LLMap = emap + cls.__EvtLL2HLMap = {val: key for key, val in emap.items()} + + names = sorted({f'Evt{i:02d}' for i in range(64)} | set(emap.values())) + codes = [int(n[3:]) for n in names] + codes, names = list(zip(*sorted(zip(codes, names)))) + cls.__EvtLL = _csdev.Const.register('EventsLL', names, values=codes) return cls.__EvtHL2LLMap @_classproperty @@ -102,6 +104,12 @@ def EvtLL2HLMap(cls): cls.EvtHL2LLMap return cls.__EvtLL2HLMap + @_classproperty + def EvtLL(cls): + """.""" + cls.EvtHL2LLMap + return cls.__EvtLL + def get_event_database(evt_num=0, prefix=None): """Return event_database.""" diff --git a/siriuspy/tests/clientconfigdb/test_configdb_client.py b/siriuspy/tests/clientconfigdb/test_configdb_client.py index c63e9f0c1..efc003e65 100755 --- a/siriuspy/tests/clientconfigdb/test_configdb_client.py +++ b/siriuspy/tests/clientconfigdb/test_configdb_client.py @@ -31,6 +31,7 @@ class TestConfigDBClient(TestCase): 'check_valid_configname', "conv_timestamp_txt_2_flt", "conv_timestamp_flt_2_txt", + "compare_configs", } def test_api(self):