class ICDevicewMotor(ICDeviceBase): """ ICDeviceBase with detector motors """ # motors x = FormattedComponent(EpicsMotor, '4iddx:{_motorx}', labels=('motors', 'detectors')) y = FormattedComponent(EpicsMotor, '4iddx:{_motory}', labels=('motors', 'detectors')) def __init__(self, prefix, name, motorx, motory, scaler_num, preamp_num, **kwargs): """ Parameters ---------- prefix : str PV prefix, here for ophyd compatibility, it won't be used. name : str Unique device name. motorx, motory : str PV sufix for the x, y motors. scaler_num : int Scaler channel number. preamp_num: int SRS pre-amplifier number. kwargs : dict Passed to `ophyd.Device` """ self._motorx = motorx self._motory = motory super().__init__(prefix, name, scaler_num, preamp_num, **kwargs)
class RubyDevice(Device): """ Ruby system """ focus = Component(EpicsMotor, 'm37', labels=('motor', 'ruby')) y = Component(EpicsMotor, 'm38', labels=('motor', 'ruby')) z = Component(EpicsMotor, 'm39', labels=('motor', 'ruby')) zoom = Component(EpicsMotor, 'm40', labels=('motor', 'ruby')) led = FormattedComponent(DAC, '4idd:DAC1_1', labels=('ruby', )) laser = FormattedComponent(DAC, '4idd:DAC1_2', labels=('ruby', ))
class AmplfierGainDevice(Device): _default_configuration_attrs = () _default_read_attrs = ('gain', 'background', 'background_error') gain = FormattedComponent(EpicsSignal, '{self.prefix}gain{self._ch_num}') background = FormattedComponent(EpicsSignal, '{self.prefix}bkg{self._ch_num}') background_error = FormattedComponent(EpicsSignal, '{self.prefix}bkgErr{self._ch_num}') def __init__(self, prefix, ch_num=None, **kwargs): assert ch_num is not None, "Must provide `ch_num=` keyword argument." self._ch_num = ch_num super().__init__(prefix, **kwargs)
class OneSlot(Device): """ Configure one filter slot. """ label = FormattedComponent(EpicsSignal, '{prefix}{_num}.DESC', kind='config', string=True) status = FormattedComponent(EpicsSignal, '{prefix}{_num}.VAL', kind='config', string=True) def __init__(self, *args, num=0, **kwargs): self._num = num super().__init__(*args, **kwargs)
class HorizontalDiffractionMirror(XYMotor): "x and y with pitch, which has different read and write PVs" #p = FormattedComponent(EpicsSignal, read_pv='{self.prefix}-Ax:P}}E-I', write_pv='{self.prefix}-Ax:P}}E-SP', add_prefix=('read_pv', 'write_pv', 'suffix')) p = FormattedComponent(EpicsSignal, read_pv='{self.prefix}-Ax:P}}Pos-I', write_pv='{self.prefix}-Ax:P}}PID-SP', add_prefix=('read_pv', 'write_pv', 'suffix'))
class ICDeviceBase(Device): """ Holds basic configuration of ion chambers/photodiodes. """ # SRS amplifier preamp = FormattedComponent(PreAmpDevice, '4idd:A{_num}', kind='config', labels=('preamps', 'detectors',)) # Scaler channel name scaler_name = Component(Signal, value=0, kind='config') def __init__(self, prefix, name, scaler_num, preamp_num, **kwargs): """ Parameters ---------- prefix : str PV prefix, here for ophyd compatibility, it won't be used. name : str Unique device name. scaler_num : int Scaler channel number. preamp_num: int SRS pre-amplifier number. kwargs : dict Passed to `ophyd.Device` """ # preamp_num self._num = preamp_num super().__init__(prefix=prefix, name=name, **kwargs) # Scaler channel name self.scaler_name.put( getattr(scalerd.channels, 'chan{:02d}'.format(scaler_num)).s.name )
class LS336Device(Device): """ Support for Lakeshore 336 temperature controller """ loop1 = FormattedComponent(LS336_LoopControl, "{prefix}", loop_number=1) loop2 = FormattedComponent(LoopSample, "{prefix}", loop_number=2) loop3 = FormattedComponent(LS336_LoopRO, "{prefix}", loop_number=3) loop4 = FormattedComponent(LS336_LoopRO, "{prefix}", loop_number=4) # same names as apstools.synApps._common.EpicsRecordDeviceCommonAll scanning_rate = Component(EpicsSignal, "read.SCAN", kind="omitted") process_record = Component(EpicsSignal, "read.PROC", kind="omitted") read_all = Component(EpicsSignal, "readAll.PROC", kind="omitted") serial = Component(AsynRecord, "serial", kind="omitted") track_vaporizer = Component(TrackingSignal, value=True, kind="config")
class PRDevice(Device): x = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[x]}', labels=('motor', 'phase retarders')) y = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[y]}', labels=('motor', 'phase retarders')) th = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[th]}', labels=('motor', 'phase retarders')) def __init__(self, prefix, name, motorsDict, **kwargs): self._motorsDict = motorsDict super().__init__(prefix=prefix, name=name, **kwargs)
class UserCalcChannel(Device): trigger_option = FormattedComponent(EpicsSignal, '{self.prefix}.IN{self._ch}P', kind=Kind.config) PVname = FormattedComponent(EpicsSignal, '{self.prefix}.IN{self._ch}N', kind=Kind.config) value = FormattedComponent(EpicsSignal, '{self.prefix}.{self._ch}', kind=Kind.config) def __init__(self, prefix, ch, **kwargs): self._ch = ch super().__init__(prefix, **kwargs)
class KohzuPositioner(PVPositionerSoftDone): stop_theta = FormattedComponent(EpicsSignal, "{_theta_pv}.STOP", kind="omitted") stop_y = FormattedComponent(EpicsSignal, "{_y_pv}.STOP", kind="omitted") stop_z = FormattedComponent(EpicsSignal, "{_z_pv}.STOP", kind="omitted") actuate = Component(EpicsSignal, "KohzuPutBO", kind="omitted") actuate_value = 1 def __init__(self, prefix, *, limits=None, readback_pv="", setpoint_pv="", name=None, read_attrs=None, configuration_attrs=None, parent=None, egu="", **kwargs): def get_motor_pv(label): _pv_signal = EpicsSignalRO(f"{prefix}Kohzu{label}PvSI", name="tmp") _pv_signal.wait_for_connection() return _pv_signal.get(as_string=True) self._theta_pv = get_motor_pv("Theta") self._y_pv = get_motor_pv("Y") self._z_pv = get_motor_pv("Z") super().__init__(prefix, limits=limits, readback_pv=readback_pv, setpoint_pv=setpoint_pv, name=name, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, egu=egu, **kwargs) def stop(self, *, success=False): for motor in ["theta", "y", "z"]: getattr(self, f"stop_{motor}").put(1, wait=False) super().stop(success=success)
class LocalPositioner(PVPositionerSoftDone): """ Voltage/Current positioner """ readback = FormattedComponent( EpicsSignalRO, '{prefix}d{_type}', kind='normal', labels=('kepko', 'magnet') ) setpoint = FormattedComponent( EpicsSignal, "{prefix}{_type}", write_pv="{prefix}set{_type}", kind='normal', labels=('kepko', 'magnet') ) def __init__(self, *args, progtype, **kwargs): self._type = progtype # TODO: This tolerance seems good, but may need to be tested. super().__init__(*args, tolerance=0.02, **kwargs)
class PIM(PIMMotor, SimDevice): detector = FormattedComponent(PIMPulnixDetector, "{self._section}:{self._imager}:CVV:01", read_attrs=['stats2']) def __init__(self, prefix, x=0, y=0, z=0, noise=0, settle_time=0, centroid_noise=False, size=(640,480), resolution=(0.0076,0.0062), zero_outside_yag=False, **kwargs): self._section=prefix self._imager=prefix if len(prefix.split(":")) < 2: prefix = "TST:{0}".format(prefix) super().__init__(prefix, pos_in=y, noise=noise, settle_time=settle_time, **kwargs) # Simulation Values self.sim_x.put(x) self.sim_y._get_readback = lambda : self._pos.value self.sim_z.put(z) self.log_pref = "{0} (PIM) - ".format(self.name) # Detector args self.zero_outside_yag = zero_outside_yag self.resolution = resolution self.size = size self.centroid_noise = centroid_noise @property def centroid_noise(self): return (self.detector.noise_x, self.detector.noise_y) @centroid_noise.setter def centroid_noise(self, val): self.detector.noise_x = bool(val) self.detector.noise_y = bool(val) @property def size(self): return self.detector.size @size.setter def size(self, val): self.detector.size = val @property def resolution(self): return self.detector.resolution @resolution.setter def resolution(self, val): self.detector.resolution = val @property def zero_outside_yag(self): return self.detector.zero_outside_yag @zero_outside_yag.setter def zero_outside_yag(self, val): self.detector.zero_outside_yag = bool(val)
class LS336_LoopRO(Device): """ loop3 and loop4 do not use the heaters. """ # Set this to normal because we don't use it. readback = FormattedComponent(EpicsSignalRO, "{prefix}IN{loop_number}", kind="normal") units = FormattedComponent(EpicsSignalWithRBV, "{prefix}IN{loop_number}:Units", kind="omitted") loop_name = FormattedComponent(EpicsSignalRO, "{prefix}IN{loop_number}:Name_RBV", kind="omitted") def __init__(self, *args, loop_number=None, **kwargs): self.loop_number = loop_number super().__init__(*args, **kwargs)
class SlitDevice(Device): # Setting motors top = FormattedComponent(EpicsMotor, '{prefix}{_motorsDict[top]}', labels=('motors', 'slits')) bot = FormattedComponent(EpicsMotor, '{prefix}{_motorsDict[bot]}', labels=('motors', 'slits')) out = FormattedComponent(EpicsMotor, '{prefix}{_motorsDict[out]}', labels=('motors', 'slits')) inb = FormattedComponent(EpicsMotor, '{prefix}{_motorsDict[inb]}', labels=('motors', 'slits')) # Setting pseudo positioners vcen = FormattedComponent(PVPositionerSoftDone, '{prefix}{_slit_prefix}', readback_pv='Vt2.D', setpoint_pv='Vcenter.VAL', labels=('slits', )) vsize = FormattedComponent(PVPositionerSoftDone, '{prefix}{_slit_prefix}', readback_pv='Vt2.C', setpoint_pv='Vsize.VAL', labels=('slits', )) hcen = FormattedComponent(PVPositionerSoftDone, '{prefix}{_slit_prefix}', readback_pv='Ht2.D', setpoint_pv='Hcenter.VAL', labels=('slits', )) hsize = FormattedComponent(PVPositionerSoftDone, '{prefix}{_slit_prefix}', readback_pv='Ht2.C', setpoint_pv='Hsize.VAL', labels=('slits', )) def __init__(self, PV, name, motorsDict, slitnum, **kwargs): self._motorsDict = motorsDict self._slit_prefix = f'Slit{slitnum}' super().__init__(prefix=PV, name=name, **kwargs)
class Monochromator(KohzuSeqCtl_Monochromator): """ Tweaks from apstools mono """ wavelength = Component(KohzuPositioner, "", readback_pv="BraggLambdaRdbkAO", setpoint_pv="BraggLambdaAO") energy = Component(KohzuPositioner, "", readback_pv="BraggERdbkAO", setpoint_pv="BraggEAO") theta = Component(KohzuPositioner, "", readback_pv="BraggThetaRdbkAO", setpoint_pv="BraggThetaAO") # No y1 at 4-ID-D y1 = None x2 = Component(EpicsMotor, 'm6', labels=('motors', 'mono')) y2 = Component(EpicsSignalRO, 'KohzuYRdbkAI', labels=('motors', 'mono')) z2 = Component(EpicsSignalRO, 'KohzuZRdbkAI', labels=('motors', 'mono')) thf2 = Component(EpicsMotor, 'm4', labels=('motors', 'mono')) chi2 = Component(EpicsMotor, 'm5', labels=('motors', 'mono')) table_x = Component(EpicsMotor, 'm7', labels=('motors', 'mono')) table_y = Component(EpicsMotor, 'm8', labels=('motors', 'mono')) feedback = FormattedComponent(MonoFeedback, '4id:') def calibrate_energy(self, value): """Calibrate the mono energy. Parameters ---------- value: float New energy for the current monochromator position. """ self.use_set.put('Set', use_complete=True) self.energy.put(value) self.use_set.put('Use', use_complete=True)
class Transfocator(Device): """ Class to represent the MFX Transfocator """ # Define EPICS signals xrt_limit = Component(EpicsSignalRO, ":XRT_ONLY") tfs_limit = Component(EpicsSignalRO, ":MFX_ONLY") faulted = Component(EpicsSignalRO, ":BEAM:FAULTED") # XRT Lenses prefocus_top = Component(Lens, ":DIA:03") prefocus_mid = Component(Lens, ":DIA:02") prefocus_bot = Component(Lens, ":DIA:01") # TFS Lenses tfs_02 = Component(Lens, ":TFS:02") tfs_03 = Component(Lens, ":TFS:03") tfs_04 = Component(Lens, ":TFS:04") tfs_05 = Component(Lens, ":TFS:05") tfs_06 = Component(Lens, ":TFS:06") tfs_07 = Component(Lens, ":TFS:07") tfs_08 = Component(Lens, ":TFS:08") tfs_09 = Component(Lens, ":TFS:09") tfs_10 = Component(Lens, ":TFS:10") # Translation translation = FormattedComponent(IMS, "MFX:TFS:MMS:21") def __init__(self, prefix, *, nominal_sample=399.88103, **kwargs): self.nominal_sample = nominal_sample super().__init__(prefix, **kwargs) @property def lenses(self): """ Component lenses """ return [getattr(self, dev) for dev in self._sub_devices if isinstance(getattr(self, dev), Lens)] @property def xrt_lenses(self): """ Lenses in the XRT """ return [lens for lens in self.lenses if 'DIA' in lens.prefix] @property def tfs_lenses(self): """ Transfocator lenses """ return [lens for lens in self.lenses if 'TFS' in lens.prefix] @property def current_focus(self): """ The distance from the focus of the Transfocator to nominal_sample Note ---- If no lenses are inserted this will retun NaN """ # Find inserted lenses inserted = [lens for lens in self.lenses if lens.inserted] # Check that we have any inserted lenses at all if not inserted: logger.warning("No lenses are currently inserted") return math.nan # Calculate the image from this set of lenses return LensConnect(*inserted).image(0.0) - self.nominal_sample def find_best_combo(self, target=None, show=True, **kwargs): """ Calculate the best lens array to hit the nominal sample point Parameters ---------- target : float, optional The target image of the lens array. By default this is `nominal_sample` show : bool, optional Print a table of the of the calculated lens combination kwargs: Passed to :meth:`.Calculator.find_solution` """ target = target or self.nominal_sample # Only included allowed XRT lenses xrt_limit = self.xrt_limit.get() allowed_xrt = [lens for lens in self.xrt_lenses if lens.radius >= xrt_limit] # Warn users if no XRT lenses are over the required radius if not allowed_xrt: logger.warning("Can not find a prefocusing lens that meets the " "safety requirements") # Create a calculator calc = Calculator(allowed_xrt, self.tfs_lenses) # Return the solution combo = calc.find_solution(target, **kwargs) combo.show_info() return combo def set(self, value, **kwargs): """ Set the Transfocator focus Parameters """ return self.focus_at(value=value, **kwargs) def focus_at(self, value=None, wait=False, timeout=None, **kwargs): """ Calculate a combination and insert the lenses Parameters ---------- value: float, optional Chosen focal plane. Nominal sample by default wait : bool, optional Wait for the motion of the transfocator to complete timeout: float, optional Timeout for motion kwargs: All passed to :meth:`.find_best_combo` Returns ------- StateStatus Status that represents whether the move is complete """ # Find the best combination of lenses to match the target image plane = value or self.nominal_sample best_combo = self.find_best_combo(target=plane, **kwargs) # Collect status to combine statuses = list() # Only tell one XRT lens to insert prefocused = False for lens in self.xrt_lenses: if lens in best_combo.lenses: statuses.append(lens.insert(timeout=timeout)) prefocused = True break # If we have no XRT lenses one remove will do if not prefocused: statuses.append(self.xrt_lenses[0].remove(timeout=timeout)) # Ensure all Transfocator lenses are correct for lens in self.tfs_lenses: if lens in best_combo.lenses: statuses.append(lens.insert(timeout=timeout)) else: statuses.append(lens.remove(timeout=timeout)) # Conglomerate all status objects status = statuses.pop(0) for st in statuses: status = status & st # Wait if necessary if wait: status_wait(status, timeout=timeout) return status
class SimulatedApsPssShutterWithStatus(Device): """ Simulated APS PSS shutter """ open_bit = Component(SynSignal) close_bit = Component(SynSignal) pss_state = FormattedComponent(SynSignal) # strings the user will use open_str = 'open' close_str = 'close' # pss_state PV values from EPICS open_val = 1 close_val = 0 # simulated response time for PSS status response_time = 0.5 def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) self.open_bit.set(0) self.close_bit.set(0) self.pss_state.set(self.close_val) def open(self, timeout=10): """request the shutter to open""" self.set(self.open_str) def close(self, timeout=10): """request the shutter to close""" self.set(self.close_str) def set(self, value, **kwargs): """set the shutter to "close" or "open" """ # first, validate the input value acceptables = (self.close_str, self.open_str) if value not in acceptables: msg = "value should be one of " + " | ".join(acceptables) msg += " : received " + str(value) raise ValueError(msg) command_signal = { self.open_str: self.open_bit, self.close_str: self.close_bit }[value] expected_value = { self.open_str: self.open_val, self.close_str: self.close_val }[value] working_status = DeviceStatus(self) simulate_delay = self.pss_state.value != expected_value def shutter_cb(value, timestamp, **kwargs): self.pss_state.clear_sub(shutter_cb) if simulate_delay: time.sleep(self.response_time) self.pss_state.set(expected_value) working_status._finished() self.pss_state.subscribe(shutter_cb) command_signal.put(1) # finally, make sure both signals are reset self.open_bit.put(0) self.close_bit.put(0) return working_status @property def isOpen(self): """is the shutter open?""" if self.pss_state.value is None: self.pss_state.set(self.close_val) return self.pss_state.value == self.open_val @property def isClosed(self): """is the shutter closed?""" if self.pss_state.value is None: self.pss_state.set(self.close_val) return self.pss_state.value == self.close_val
class FourCircleDiffractometer(E4CV): """ E4CV: huber diffractometer in 4-circle vertical geometry with energy. 4-ID-D setup. """ # HKL and 4C motors h = Component(PseudoSingle, '', labels=("hkl", "fourc")) k = Component(PseudoSingle, '', labels=("hkl", "fourc")) l = Component(PseudoSingle, '', labels=("hkl", "fourc")) theta = Component(EpicsMotor, 'm65', labels=("motor", "fourc")) chi = Component(EpicsMotor, 'm67', labels=("motor", "fourc")) phi = Component(EpicsMotor, 'm68', labels=("motor", "fourc")) tth = Component(EpicsMotor, 'm66', labels=("motor", "fourc")) th_tth_min = Component(EpicsSignal, "userCalc1.C", labels=('fourc', 'limits'), kind='config') th_tth_permit = Component(EpicsSignal, "userCalc1.VAL", labels=('fourc', 'limits'), kind='config') # Explicitly selects the real motors _real = ['theta', 'chi', 'phi', 'tth'] # Cryo carrier x = Component(EpicsMotor, 'm14', labels=('motor', 'fourc')) # Cryo X y = Component(EpicsMotor, 'm15', labels=('motor', 'fourc')) # Cryo Y z = Component(EpicsMotor, 'm16', labels=('motor', 'fourc')) # Cryo Z # Base motors baseth = Component(EpicsMotor, 'm69', labels=('motor', 'fourc')) basetth = Component(EpicsMotor, 'm70', labels=('motor', 'fourc')) tablex = Component(EpicsMotor, 'm18', labels=('motor', 'fourc')) tabley = Component(EpicsMotor, 'm17', labels=('motor', 'fourc')) # Analyzer motors ath = Component(EpicsMotor, 'm77', labels=('motor', 'fourc')) achi = Component(EpicsMotor, 'm79', labels=('motor', 'fourc')) atth = Component(EpicsMotor, 'm78', labels=('motor', 'fourc')) # Energy energy = FormattedComponent(EpicsSignalRO, "4idb:BraggERdbkAO", kind='omitted') # energy_EGU = Component(EpicsSignal, "optics:energy.EGU") energy_update_calc_flag = Component(Signal, value=1) energy_offset = Component(Signal, value=0) # TODO: This is needed to prevent busy plotting. @property def hints(self): fields = [] for _, component in self._get_components_of_kind(Kind.hinted): if (~Kind.normal & Kind.hinted) & component.kind: c_hints = component.hints fields.extend(c_hints.get('fields', [])) return {'fields': fields}
class SlitDevice(Device): ## Setting motors ## top = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[top]}', labels=('motor', 'slit')) bot = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[bot]}', labels=('motor', 'slit')) out = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[out]}', labels=('motor', 'slit')) inb = FormattedComponent(EpicsMotor, '{self.prefix}:{_motorsDict[inb]}', labels=('motor', 'slit')) ## Setting pseudo positioners ## vcen = FormattedComponent(EpicsSignal, '{self.prefix}:{_signalsDict[vcen][0]}', write_pv='{self.prefix}:{_signalsDict[vcen][1]}', labels=('slit')) hcen = FormattedComponent(EpicsSignal, '{self.prefix}:{_signalsDict[hcen][0]}', write_pv='{self.prefix}:{_signalsDict[hcen][1]}', labels=('slit')) vsize = FormattedComponent( EpicsSignal, '{self.prefix}:{_signalsDict[vsize][0]}', write_pv='{self.prefix}:{_signalsDict[vsize][1]}', labels=('slit')) hsize = FormattedComponent( EpicsSignal, '{self.prefix}:{_signalsDict[hsize][0]}', write_pv='{self.prefix}:{_signalsDict[hsize][1]}', labels=('slit')) def __init__(self, PV, name, motorsDict, slitnum=None, signalsDict=None, **kwargs): self._motorsDict = motorsDict if signalsDict is None: if type(slitnum) != int: raise TypeError( 'If signalsDict is None, then slitnum has to be an integer!' ) else: prefix = 'Slit{}'.format(slitnum) self._signalsDict = { 'vcen': [prefix + 'Vt2.D', prefix + 'Vcenter'], 'vsize': [prefix + 'Vt2.C', prefix + 'Vsize'], 'hcen': [prefix + 'Ht2.D', prefix + 'Hcenter'], 'hsize': [prefix + 'Ht2.C', prefix + 'Hsize'] } else: self._signalsDict = signalsDict super().__init__(prefix=PV, name=name, **kwargs)
class Xspress3Channel(Device): rois = FormattedComponent(ROIDevice, '{prefix}MCA{_chnum}ROI:') # Timestamp --> it's used to tell when the ROI plugin is done. timestamp = FormattedComponent(EpicsSignalRO, '{prefix}MCA{_chnum}ROI:TimeStamp_RBV', kind='omitted', auto_monitor=True) # SCAs clock_ticks = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA0:Value_RBV') reset_ticks = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA1:Value_RBV') reset_counts = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA2:Value_RBV') all_events = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA3:Value_RBV') all_good = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA4:Value_RBV') pileup = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA7:Value_RBV') dt_factor = FormattedComponent(EpicsSignalRO, '{prefix}C{_chnum}SCA8:Value_RBV') def __init__(self, *args, chnum, **kwargs): # TODO: I don't like how this is currently implemented, but it works. self._chnum = chnum super().__init__(*args, **kwargs) def _status_done(self): # Create status that checks when the timestamp updates. status = Status(self.timestamp, settle_time=0.01) def _set_finished(**kwargs): status.set_finished() self.timestamp.clear_sub(_set_finished) self.timestamp.subscribe(_set_finished, event_type='value', run=False) return status @property def all_rois(self): for roi in range(1, self.rois.num_rois.get() + 1): yield getattr(self.rois, 'roi{:02d}'.format(roi)) def set_roi(self, index, ev_low, ev_size, name=None, enable=True): '''Set specified ROI to (ev_low, ev_size) Parameters ---------- index : int or list of int ROI index. It can be passed as an integer or an iterable with integers. ev_low : int low eV setting. ev_size : int size eV setting. name : str, optional ROI name, if nothing is passed it will keep the current name. enable : boolean Flag to enable the ROI. ''' if isinstance(index, int): index = [index] rois = list(self.all_rois) for ind in index: if ind <= 0: raise ValueError('ROI index starts from 1') roi = rois[ind - 1] if not name: name = roi.roi_name.get() roi.configure(name, ev_low, ev_size, enable=enable)
class LS340Device(Device): """ Lakeshore 340 setup EPICS version 1.1 """ control = FormattedComponent(LS340_LoopControl, "{prefix}", loop_number=1) sample = FormattedComponent(LS340_LoopSample, "{prefix}", loop_number=2) heater = Component(EpicsSignalRO, "Heater") heater_range = Component(EpicsSignal, "Rg_rdbk", write_pv="HeatRg", kind="normal", put_complete=True) auto_heater = Component(TrackingSignal, value=False, kind="config") read_pid = Component(EpicsSignal, "readPID.PROC", kind="omitted") # same names as apstools.synApps._common.EpicsRecordDeviceCommonAll scanning_rate = Component(EpicsSignal, "read.SCAN", kind="omitted") process_record = Component(EpicsSignal, "read.PROC", kind="omitted") serial = Component(AsynRecord, "serial", kind="omitted") # This must be modified either here, or before using auto_heater. _auto_ranges = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # TODO: I don't know why this has to be done, otherwise it gets hinted. self.control.readback.kind = "normal" @auto_heater.sub_value def _subscribe_auto_heater(self, value=None, **kwargs): if value: self.control.setpoint.subscribe(self._switch_heater, event_type='value') else: self.control.setpoint.clear_subs(self._switch_heater) def _switch_heater(self, value=None, **kwargs): # TODO: Find a better way to do this, perhaps with an array? for _heater_range, _temp_range in self._auto_ranges.items(): if _temp_range: if _temp_range[0] < value <= _temp_range[1]: self.heater_range.put(_heater_range) @property def auto_ranges(self): return self._auto_ranges @auto_ranges.setter def auto_ranges(self, value): if not isinstance(value, dict): raise TypeError('auto_ranges must be a dictionary.') for _heater_range, _temp_range in value.items(): if _heater_range not in self.heater_range.enum_strs: raise ValueError("The input dictionary keys must be one of " f"these: {self.heater_range.enum_strs}, but " f"{_heater_range} was entered.") if _temp_range is not None and len(_temp_range) != 2: raise ValueError(f"The value {_temp_range} is invalid! It " "must be either None or an iterable with two " "items.") self._auto_ranges = value
class PVPositionerSoftDone(PVPositioner): # positioner readback = FormattedComponent(EpicsSignalRO, "{prefix}{_readback_pv}", kind="hinted", auto_monitor=True) setpoint = FormattedComponent(EpicsSignal, "{prefix}{_setpoint_pv}", kind="normal", put_complete=True) done = Component(Signal, value=True, kind="config") done_value = True # tolerance always updated during init. tolerance = Component(Signal, value=1, kind="config") report_dmov_changes = Component(Signal, value=False, kind="omitted") def cb_readback(self, *args, **kwargs): """ Called when readback changes (EPICS CA monitor event). """ diff = self.readback.get() - getattr(self, self._target_attr).get() dmov = abs(diff) <= self.tolerance.get() if self.report_dmov_changes.get() and dmov != self.done.get(): logger.debug(f"{self.name} reached: {dmov}") self.done.put(dmov) def cb_setpoint(self, *args, **kwargs): """ Called when setpoint changes (EPICS CA monitor event). When the setpoint is changed, force done=False. For any move, done must go != done_value, then back to done_value (True). Without this response, a small move (within tolerance) will not return. Next update of readback will compute self.done. """ self.done.put(not self.done_value) def __init__(self, prefix, *, limits=None, readback_pv="", setpoint_pv="", name=None, read_attrs=None, configuration_attrs=None, parent=None, egu="", tolerance=None, target_attr="setpoint", **kwargs): self._setpoint_pv = setpoint_pv self._readback_pv = readback_pv self._target_attr = target_attr super().__init__(prefix=prefix, limits=limits, name=name, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, egu=egu, **kwargs) # Make the default alias for the readback the name of the # positioner itself as in EpicsMotor. self.readback.name = self.name self.readback.subscribe(self.cb_readback) self.setpoint.subscribe(self.cb_setpoint) if tolerance is None: self.readback.wait_for_connection(timeout=5.0) self.setpoint.wait_for_connection(timeout=5.0) rb = self.readback.precision sp = self.setpoint.precision tolerance = rb if rb >= sp else sp self.tolerance.put(tolerance) def _setup_move(self, position): '''Move and do not wait until motion is complete (asynchronous)''' self.log.debug('%s.setpoint = %s', self.name, position) self.setpoint.put(position, wait=False) if self.actuate is not None: self.log.debug('%s.actuate = %s', self.name, self.actuate_value) self.actuate.put(self.actuate_value, wait=False)
class LS336_LoopControl(PVPositionerSoftDone): """ Setup for loop with heater control. The lakeshore 336 accepts up to two heaters. """ # position # TODO: Not sure this will work. May need to separate the RO again. readback = FormattedComponent(EpicsSignalRO, "{prefix}IN{loop_number}", auto_monitor=True, kind="hinted") setpoint = FormattedComponent(EpicsSignalWithRBV, "{prefix}OUT{loop_number}:SP", put_complete=True, kind="normal") # Due to ramping the setpoint will change slowly and the readback may catch # up even if the motion is not done. target = Component(Signal, value=0, kind="omitted") heater = FormattedComponent(EpicsSignalRO, "{prefix}HTR{loop_number}", auto_monitor=True, kind="normal") # configuration units = FormattedComponent(EpicsSignalWithRBV, "{prefix}IN{loop_number}:Units", kind="config") pid_P = FormattedComponent(EpicsSignalWithRBV, "{prefix}P{loop_number}", kind="config") pid_I = FormattedComponent(EpicsSignalWithRBV, "{prefix}I{loop_number}", kind="config") pid_D = FormattedComponent(EpicsSignalWithRBV, "{prefix}D{loop_number}", kind="config") ramp_rate = FormattedComponent(EpicsSignalWithRBV, "{prefix}RampR{loop_number}", kind="config") ramp_on = FormattedComponent(EpicsSignalWithRBV, "{prefix}OnRamp{loop_number}", kind="config") loop_name = FormattedComponent(EpicsSignalRO, "{prefix}IN{loop_number}:Name_RBV", kind="config") control = FormattedComponent(EpicsSignalWithRBV, "{prefix}OUT{loop_number}:Cntrl", kind="config") manual = FormattedComponent(EpicsSignalWithRBV, "{prefix}OUT{loop_number}:MOUT", kind="config") mode = FormattedComponent(EpicsSignalWithRBV, "{prefix}OUT{loop_number}:Mode", kind="config") heater_range = FormattedComponent(EpicsSignalWithRBV, "{prefix}HTR{loop_number}:Range", kind="config", auto_monitor=True, string=True) auto_heater = Component(TrackingSignal, value=False, kind="config") # This must be modified either here, or before using auto_heater. _auto_ranges = None def __init__(self, *args, loop_number=None, timeout=60 * 60 * 10, **kwargs): self.loop_number = loop_number super().__init__(*args, timeout=timeout, tolerance=0.1, target_attr="target", **kwargs) self._settle_time = 0 @property def settle_time(self): return self._settle_time @settle_time.setter def settle_time(self, value): if value < 0: raise ValueError('Settle value needs to be >= 0.') else: self._settle_time = value @property def egu(self): return self.units.get(as_string=True) def stop(self, *, success=False): if success is False: self.setpoint.put(self._position) super().stop(success=success) def pause(self): self.setpoint.put(self._position) def move(self, position, **kwargs): # Need to update the target. self.target.put(position) return super().move(position, **kwargs) @auto_heater.sub_value def _subscribe_auto_heater(self, value=None, **kwargs): if value: self.setpointRO.subscribe(self._switch_heater, event_type='value') else: self.setpointRO.clear_subs(self._switch_heater) def _switch_heater(self, value=None, **kwargs): # TODO: Find a better way to do this, perhaps with an array? for _heater_range, _temp_range in self._auto_ranges.items(): if _temp_range: if _temp_range[0] < value <= _temp_range[1]: self.heater_range.put(_heater_range) @property def auto_ranges(self): return self._auto_ranges @auto_ranges.setter def auto_ranges(self, value): if not isinstance(value, dict): raise TypeError('auto_ranges must be a dictionary.') for _heater_range, _temp_range in value.items(): if _heater_range not in self.heater_range.enum_strs: raise ValueError("The input dictionary keys must be one of " f"these: {self.heater_range.enum_strs}, but " f"{_heater_range} was entered.") if _temp_range is not None and len(_temp_range) != 2: raise ValueError(f"The value {_temp_range} is invalid! It " "must be either None or an iterable with two " "items.") self._auto_ranges = value
class LS340_LoopBase(PVPositionerSoftDone): """ Base settings for both sample and control loops. """ # position readback = Component(Signal, value=0) setpoint = FormattedComponent(SetpointSignal, "{prefix}SP{loop_number}", write_pv="{prefix}wr_SP{loop_number}", kind="normal", put_complete=True) # This is here only because of ramping, because then setpoint will change # slowly. target = Component(Signal, value=0, kind="omitted") # configuration units = Component(Signal, value='K', kind="config") pid_P = FormattedComponent(EpicsSignal, "{prefix}P{loop_number}", write_pv='{prefix}setPID{loop_number}.AA', kind="config") pid_I = FormattedComponent(EpicsSignal, "{prefix}I{loop_number}", write_pv='{prefix}setPID{loop_number}.BB', kind="config") pid_D = FormattedComponent(EpicsSignal, "{prefix}D{loop_number}", write_pv='{prefix}setPID{loop_number}.CC', kind="config") ramp_rate = FormattedComponent(EpicsSignal, "{prefix}Ramp{loop_number}", write_pv='{prefix}setRamp{loop_number}.BB', kind="config") ramp_on = FormattedComponent(EpicsSignal, "{prefix}Ramp{loop_number}_on", kind="config") def __init__(self, *args, loop_number=None, timeout=60*60*10, **kwargs): self.loop_number = loop_number super().__init__(*args, timeout=timeout, tolerance=0.1, target_attr="target", **kwargs) self._settle_time = 0 @property def settle_time(self): return self._settle_time @settle_time.setter def settle_time(self, value): if value < 0: raise ValueError('Settle value needs to be >= 0.') else: self._settle_time = value @property def egu(self): return self.units.get(as_string=True) def stop(self, *, success=False): if success is False: self.setpoint.put(self._position) super().stop(success=success) def pause(self): self.setpoint.put(self._position, wait=True) def move(self, position, **kwargs): # Need to update the target. self.target.put(position) return super().move(position, **kwargs)