Skip to content

Commit

Permalink
Merge pull request #1179 from KaushikMalapati/tmodevs
Browse files Browse the repository at this point in the history
 TMO GMD device classes
  • Loading branch information
ZLLentz authored Nov 9, 2023
2 parents 17b0a0f + ab8ccfd commit 6494716
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 39 deletions.
31 changes: 31 additions & 0 deletions docs/source/upcoming_release_notes/1179-tmo_gmd_classes.rst
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions pcdsdevices/attenuator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
Expand Down Expand Up @@ -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]
Expand Down
49 changes: 45 additions & 4 deletions pcdsdevices/energy_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand All @@ -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.
Expand All @@ -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):
Expand Down
63 changes: 63 additions & 0 deletions pcdsdevices/keithley.py
Original file line number Diff line number Diff line change
@@ -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
37 changes: 2 additions & 35 deletions pcdsdevices/pim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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']
)
Expand Down

0 comments on commit 6494716

Please sign in to comment.