diff --git a/docs/source/upcoming_release_notes/1179-tmo_gmd_classes.rst b/docs/source/upcoming_release_notes/1179-tmo_gmd_classes.rst new file mode 100644 index 00000000000..f6aee871320 --- /dev/null +++ b/docs/source/upcoming_release_notes/1179-tmo_gmd_classes.rst @@ -0,0 +1,31 @@ +1179 tmo_gmd_classes +################# + +API Breaks +---------- +- Moving K2700 and IM3L0_K2700 to keithley.py + +Features +-------- +- N/A + +Device Updates +-------------- +- N/A + +New Devices +----------- +- K6514, GMD (previously unimplemented), GMDPreAmp, SXRGasAtt +- Taken from /cds/group/pcds/pyps/apps/hutch-python/tmo/tmo/tmo_beamline_control.py with some modifications + +Bugfixes +-------- +- N/A + +Maintenance +----------- +- N/A + +Contributors +------------ +- KaushikMalapati diff --git a/pcdsdevices/attenuator.py b/pcdsdevices/attenuator.py index 63ae037adac..0e432cd364d 100644 --- a/pcdsdevices/attenuator.py +++ b/pcdsdevices/attenuator.py @@ -18,6 +18,7 @@ from ophyd.signal import EpicsSignal, EpicsSignalRO, Signal, SignalRO from . import utils +from .device import GroupDevice from .device import UnrelatedComponent as UCpt from .device import UpdateComponent as UpCpt from .epics_motor import BeckhoffAxisNoOffset @@ -28,6 +29,7 @@ from .signal import InternalSignal, MultiDerivedSignal, MultiDerivedSignalRO from .type_hints import OphydDataType, SignalToValue from .utils import get_status_float, get_status_value +from .valve import VCN, VVC from .variety import set_metadata logger = logging.getLogger(__name__) @@ -1716,6 +1718,88 @@ def is_moving(self) -> bool: return self == LadderBladeState.Moving +class SXRGasAtt(BaseInterface, GroupDevice): + tab_component_names = True + tab_whitelist = ['setup_mode'] + + transmission = Cpt(EpicsSignal, ':TRANS_RBV', write_pv=':TRANS_SP', kind='hinted', + doc='Transmission') + arb_req = Cpt(EpicsSignalRO, ':TRANS_REQ_RBV', kind='hinted', + doc='Requested transmission') + pressure = Cpt(EpicsSignal, ':GCM:82:PRESS_RBV', write_pv=':CNTRL:SP', kind='hinted', + doc='Pressure') + pressure_setpoint_rbv = Cpt(EpicsSignalRO, ':CNTRL:SP_RBV', kind='omitted', + doc='Pressure setpoint') + mode = Cpt(EpicsSignal, ':MODE_RBV', write_pv=':MODE', string=True, kind='hinted', + doc='PMPS mode') + control_enable = Cpt(EpicsSignal, ':CNTRL:ON_RBV', write_pv=':CNTRL:ON', kind='hinted', + doc='') + pressure_control_enable = Cpt(EpicsSignal, ':MODE:PressureControl_RBV', write_pv=':MODE:PressureControl', kind='hinted', + doc='Pressure control mode') + gas_type = Cpt(EpicsSignalRO, ':GAS_TYPE_RBV', string=True, kind='hinted', + doc='Selected gas') + at_target = Cpt(EpicsSignalRO, ':AtTarget_RBV', string=True, kind='hinted', + doc='At target') + moving = Cpt(EpicsSignalRO, ':Moving_RBV', string=True, kind='hinted', + doc='Moving') + gas_att_ok = Cpt(EpicsSignalRO, ':OK_RBV', string=True, kind='hinted', + doc='Ok') + transmission_setpoint_rbv = Cpt(EpicsSignalRO, ':TRANS_SP_RBV', kind='omitted', + doc='Transmission setpoint') + pressure_control_valve = Cpt(EpicsSignalRO, ':VCN:70:POS_REQ_RBV', kind='omitted', + doc='Requested position') + valve_n2 = Cpt(VVC, ':VVC:72', kind='hinted', doc='Valve n2') + valve_ar = Cpt(VVC, ':VVC:71', kind='hinted', doc='Valve ar') + valve_pressure_control = Cpt(VCN, ':VCN:70', kind='omitted', doc='Pressure control valve') + + def setup_mode(self, mode, control_type='transmission', gas_type=None): + """ + Setup gas attenuator to work in "PMPS" or "Local" mode, with either "transmission control" or "pressure control" + + Parameters + ---------- + mode : str, either "PMPS" or "Local" + Mode for attenuator. + control_type : str, optional + Set control type in "Local" mode, either "transmission" or "pressure" control. The default is 'transmission'. + gas_type : str, optional + Change gas type to "N2" or "Ar". The default is None.If None is passed the attenuator uses the current gas. + + """ + if mode is not ('PMPS' or 'Local'): + print('unrecognizied mode, options are "PMPS" or "Local"') + return + elif mode == "Local": + if control_type is not ('transmission' or 'pressure'): + print('unrecognizied control type, options are "transmission" or "pressure"') + return + if gas_type is not ('N2' or 'Ar' or None): + print('unrecognizied gas type, options are "N2", "Ar", or None') + return + + if mode == 'PMPS': + self.mode.put('PMPS') + elif mode == 'Local': + self.mode.put('Local') + + if gas_type is not None: + self.valve_ar.open_command.put(0) + self.valve_n2.open_command.put(0) + if gas_type == 'N2': + self.valve_n2.open_command.put(1) + elif gas_type == 'Ar': + self.valve_ar.open_command.put(1) + + elif mode == 'Local': + if control_type == 'transmission': + self.transmission.put(1) + self.control_enable.put(1) + elif control_type == 'pressure': + self.pressure_control_enable.put(1) + self.control_enable.put(1) + self.pressure.put(0) + + def get_blade_enum(value): try: return BladeStateEnum[value] diff --git a/pcdsdevices/energy_monitor.py b/pcdsdevices/energy_monitor.py index 9722e20ba5e..7b00679a9e2 100644 --- a/pcdsdevices/energy_monitor.py +++ b/pcdsdevices/energy_monitor.py @@ -5,9 +5,10 @@ from ophyd import Component as Cpt from ophyd import Device -from ophyd.signal import SignalRO +from ophyd.signal import EpicsSignal, EpicsSignalRO, SignalRO from .interface import BaseInterface +from .keithley import K6514 logger = logging.getLogger(__name__) @@ -31,6 +32,18 @@ class GEM(BaseInterface, Device): value="Not Implemented", kind='normal') +class GMDPreAmp(BaseInterface, Device): + tab_component_names = True + ion_pre_att = Cpt(EpicsSignal, ':AMP_PREATTN1_RBV', write_pv=':AMP_PREATTN1', string=True, kind='normal', doc='Channel 1 PRE attenuator') + ion_post_att = Cpt(EpicsSignal, ':AMP_POSATTN1_RBV', write_pv=':AMP_POSATTN1', string=True, kind='normal', doc='Channel 1 POS attenuator') + elec1_pre_att = Cpt(EpicsSignal, ':AMP_PREATTN2_RBV', write_pv=':AMP_PREATTN2', string=True, kind='normal', doc='Channel 2 PRE attenuator') + elec1_post_att = Cpt(EpicsSignal, ':AMP_POSATTN2_RBV', write_pv=':AMP_POSATTN2', string=True, kind='normal', doc='Channel 2 POS attenuator') + elec2_pre_att = Cpt(EpicsSignal, ':AMP_PREATTN3_RBV', write_pv=':AMP_PREATTN3', string=True, kind='normal', doc='Channel 3 PRE attenuator') + elec2_post_att = Cpt(EpicsSignal, ':AMP_POSATTN3_RBV', write_pv=':AMP_POSATTN3', string=True, kind='normal', doc='Channel 3 POS attenuator') + spare_pre_att = Cpt(EpicsSignal, ':AMP_PREATTN4_RBV', write_pv=':AMP_PREATTN4', string=True, kind='normal', doc='Channel 4 PRE attenuator') + spare_post_att = Cpt(EpicsSignal, ':AMP_POSATTN4_RBV', write_pv=':AMP_POSATTN4', string=True, kind='normal', doc='Channel 4 POS attenuator') + + class GMD(BaseInterface, Device): """ Gas Monitor Detector, installed in the LCLS-II XTES project. @@ -45,9 +58,37 @@ class GMD(BaseInterface, Device): name : str Name to refer to the GMD. """ - - not_implemented = Cpt(SignalRO, name="Not Implemented", - value="Not Implemented", kind='normal') + tab_component_names = True + avg_int = Cpt(EpicsSignalRO, ':HPS:AvgPulseIntensity', kind='hinted', + doc='Avg Pulse energy [mJ]') + mj = Cpt(EpicsSignalRO, ':HPS:milliJoulesPerPulse', kind='hinted', + doc='Pulse energy [mJ]') + photons = Cpt(EpicsSignalRO, ':HPS:AvgPhotonsPerPulse', kind='hinted', + doc='photons') + transmission = Cpt(EpicsSignalRO, ':HPS:AvgTransmission', kind='hinted', + doc='transmission') + gas_type = Cpt(EpicsSignalRO, ':GAS_TYPE_RBV', string=True, kind='hinted', + doc='Gas Type') + mean_charge = Cpt(EpicsSignal, ':HPS:MeanCharge', write_pv=':HPS:MeanCharge:Manual', kind='normal', + doc='Mean Charge used in energy calculation') + xsection = Cpt(EpicsSignal, ':HPS:CrossSection', write_pv=':HPS:CrossSection:Manual', kind='normal', + doc='Photoionization cross section used in energy calculation') + keithley_sum = Cpt(EpicsSignalRO, ':HPS:KeithleySum', kind='normal', + doc='') + pressure = Cpt(EpicsSignalRO, ':GSR:1:Calib:Pressure:Calc', kind='normal', + doc='Gas pressure in energy monitor') + mean_charge_source = Cpt(EpicsSignal, ':HPS:MeanCharge:Source', string=True, kind='omitted', + doc='Source value of mean charge (Gas Table or Manual) for energy calculation') + xsection_source = Cpt(EpicsSignal, ':HPS:MeanCharge:Source', string=True, kind='omitted', + doc='Source value of photoionization cross section (Gas Table or Manual) for energy calculation') + temperature = Cpt(EpicsSignalRO, ':RTD:1:TEMP_RBV', kind='hinted', doc='') + beam_position_x = Cpt(EpicsSignalRO, ':HPS:PosXSLOW', kind='hinted', + doc='beam position x in GMD') + beam_position_y = Cpt(EpicsSignalRO, ':HPS:PosYSLOW', kind='hinted', + doc='beam position y in GMD') + preamp = Cpt(GMDPreAmp, ':HPS', kind='omitted') + keithley1 = Cpt(K6514, ':ETM:01', kind='omitted') + keithley2 = Cpt(K6514, ':ETM:02', kind='omitted') class XGMD(BaseInterface, Device): diff --git a/pcdsdevices/keithley.py b/pcdsdevices/keithley.py new file mode 100644 index 00000000000..2c0251abb04 --- /dev/null +++ b/pcdsdevices/keithley.py @@ -0,0 +1,63 @@ +""" +Module for Keithely classes. +""" +from ophyd.device import Component as Cpt +from ophyd.device import Device +from ophyd.signal import EpicsSignal, EpicsSignalRO + +from .interface import BaseInterface + + +class K6514(BaseInterface, Device): + """ + Keithley 6514 electrometer. + + Used by GMD. + """ + tab_component_names = True + + avg_enable = Cpt(EpicsSignal, ':AvgEnable', kind='normal', doc='Enable/disable the average function') + avg_count = Cpt(EpicsSignal, ':GetAvgCount', write_pv='PutAvgCount', kind='normal', doc='Get the averaging factor') + avg_mode = Cpt(EpicsSignal, ':PutAvgType', string=True, kind='normal', doc='Select filter control: Moving or Repeated') + auto_range = Cpt(EpicsSignal, ':PutAutoRange', kind='normal', doc='Enable/disable autorange') + current_range = Cpt(EpicsSignal, ':SelectCurrentRange', string=True, kind='normal', doc='Set range in amps') + damping = Cpt(EpicsSignal, ':PutDamping', string=True, kind='normal', doc='Enable/disable current damping') + integration_time = Cpt(EpicsSignal, ':GetIntCycles', write_pv=':PutIntCycles', kind='hinted', doc='Set integration time') + measurement_function = Cpt(EpicsSignal, ':PutFunction', string=True, kind='hinted', doc='Select function - Voltage, Current, Resistance, or Charge') + reading_rate = Cpt(EpicsSignal, ':Reading.SCAN', string=True, kind='normal', doc='Reading scan rate/type') + reading = Cpt(EpicsSignalRO, ':Reading', kind='hinted', doc='Trigger and return a new measurement') + zero_correct = Cpt(EpicsSignal, ':ZeroCorrect', kind='normal', doc='Run zero-correct') + + +class K2700(BaseInterface, Device): + """ + Keithley 2700 digital multimeter. + + Currently supports reading voltage and current, direct and + alternating, but can be extended to measure other quantities + (resistance, temperature), have configurable range and integration + time, and allow for remote control of a K2700. + """ + idn = Cpt(EpicsSignalRO, ":Identity", kind="normal", + doc='Identity (name) of this device') + reading = Cpt(EpicsSignalRO, ":Reading", kind="normal", + doc='Trigger and return a new measurement') + dcv_range = Cpt(EpicsSignalRO, ":GetDCV", kind="normal", + doc='DC voltage range') + acv_range = Cpt(EpicsSignalRO, ":GetACV", kind="normal", + doc='AC voltage range') + dci_range = Cpt(EpicsSignalRO, ":GetDCI", kind="normal", + doc='DC current range') + aci_range = Cpt(EpicsSignalRO, ":GetACI", kind="normal", + doc='AC current range') + + +class IM3L0_K2700(K2700): + """ + One-off subclass of K2700 to use a pydm screen specific to this device. + + Identical to K2700 class, but uses a pydm screen for this particular device + in place of the default detailed screen. To be used in conjunction with + IM3L0 as this Keithley is added to that imager for detailed power readouts. + """ + pass diff --git a/pcdsdevices/pim.py b/pcdsdevices/pim.py index deb39c6d74d..5ced136862b 100644 --- a/pcdsdevices/pim.py +++ b/pcdsdevices/pim.py @@ -13,7 +13,7 @@ from ophyd.device import Device from ophyd.device import FormattedComponent as FCpt from ophyd.ophydobj import OphydObject -from ophyd.signal import EpicsSignal, EpicsSignalRO +from ophyd.signal import EpicsSignal from .analog_signals import FDQ from .areadetector.detectors import (PCDSAreaDetectorEmbedded, @@ -23,6 +23,7 @@ from .epics_motor import IMS, BeckhoffAxisNoOffset from .inout import InOutRecordPositioner from .interface import BaseInterface, LightpathInOutCptMixin +from .keithley import IM3L0_K2700 from .pmps import TwinCATStatePMPS from .sensors import TwinCATThermocouple from .signal import PytmcSignal @@ -559,40 +560,6 @@ class IM2K0(LCLS2ImagerBase): # Nothing else! No power meter, no zoom/focus, no filter wheel... -class K2700(BaseInterface, Device): - """ - Keithley 2700 digital multimeter. - - Currently supports reading voltage and current, direct and - alternating, but can be extended to measure other quantities - (resistance, temperature), have configurable range and integration - time, and allow for remote control of a K2700. - """ - idn = Cpt(EpicsSignalRO, ":Identity", kind="normal", - doc='Identity (name) of this device') - reading = Cpt(EpicsSignalRO, ":Reading", kind="normal", - doc='Trigger and return a new measurement') - dcv_range = Cpt(EpicsSignalRO, ":GetDCV", kind="normal", - doc='DC voltage range') - acv_range = Cpt(EpicsSignalRO, ":GetACV", kind="normal", - doc='AC voltage range') - dci_range = Cpt(EpicsSignalRO, ":GetDCI", kind="normal", - doc='DC current range') - aci_range = Cpt(EpicsSignalRO, ":GetACI", kind="normal", - doc='AC current range') - - -class IM3L0_K2700(K2700): - """ - One-off subclass of K2700 to use a pydm screen specific to this device. - - Identical to K2700 class, but uses a pydm screen for this particular device - in place of the default detailed screen. To be used in conjunction with - IM3L0 as this Keithley is added to that imager for detailed power readouts. - """ - pass - - @reorder_components( end_with=['k2700', 'power_meter', 'yag_thermocouple', 'led'] )