From 749b4a42f49598211bb06ce342f2d9f1486885db Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Fri, 4 Oct 2024 10:55:54 -0500 Subject: [PATCH] Refactored threaded ophyd devices. Moved existing threaded ophyd device loaders to the new Instrument() class. Also got rid of the dedicated "Camera" class, since it's really just an area detector. There are no longer any ``load_()`` style loading functions. --- src/conftest.py | 2 +- src/haven/__init__.py | 3 - src/haven/devices/__init__.py | 2 +- src/haven/devices/aerotech.py | 29 --- src/haven/devices/aps.py | 9 +- src/haven/devices/area_detector.py | 83 ++++++--- src/haven/devices/beamline_manager.py | 22 +-- src/haven/devices/camera.py | 133 ------------- src/haven/devices/dxp.py | 27 +-- src/haven/devices/energy_positioner.py | 21 +-- src/haven/devices/heater.py | 15 -- src/haven/devices/lerix.py | 40 ++-- src/haven/devices/power_supply.py | 27 +-- src/haven/devices/robot.py | 17 +- src/haven/devices/scaler.py | 21 --- src/haven/devices/shutter.py | 27 +-- src/haven/devices/slits.py | 42 +---- src/haven/devices/stage.py | 28 --- src/haven/devices/xia_pfcu.py | 39 +--- src/haven/devices/xspress.py | 23 --- src/haven/iconfig_testing.toml | 174 ++++++++++-------- src/haven/load_instrument.py | 82 +++------ src/haven/tests/test_aerotech.py | 10 - src/haven/tests/test_aps.py | 10 +- src/haven/tests/test_area_detector.py | 6 +- src/haven/tests/test_beamline_manager.py | 7 +- src/haven/tests/test_camera.py | 70 ------- src/haven/tests/test_energy_positioner.py | 10 +- .../tests/test_fluorescence_detectors.py | 26 +-- src/haven/tests/test_heater.py | 9 - src/haven/tests/test_iconfig.py | 7 +- src/haven/tests/test_instrument.py | 2 +- src/haven/tests/test_lerix.py | 10 - src/haven/tests/test_power_supply.py | 11 +- src/haven/tests/test_robot.py | 22 +-- src/haven/tests/test_run_engine.py | 18 +- src/haven/tests/test_scaler.py | 12 +- src/haven/tests/test_shutter.py | 22 +-- src/haven/tests/test_slits.py | 14 -- src/haven/tests/test_stages.py | 6 - src/haven/tests/test_xia_pfcu.py | 29 --- 41 files changed, 256 insertions(+), 911 deletions(-) delete mode 100644 src/haven/devices/camera.py delete mode 100644 src/haven/tests/test_camera.py diff --git a/src/conftest.py b/src/conftest.py index 52cf6a54..d4cbdc71 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -19,7 +19,7 @@ from haven.catalog import Catalog from haven.devices.aps import ApsMachine from haven.devices.beamline_manager import BeamlineManager, IOCManager -from haven.devices.camera import AravisDetector +from haven.devices.area_detector import AravisDetector from haven.devices.dxp import DxpDetector from haven.devices.dxp import add_mcas as add_dxp_mcas from haven.devices.ion_chamber import IonChamber diff --git a/src/haven/__init__.py b/src/haven/__init__.py index 807c2356..aec03f05 100644 --- a/src/haven/__init__.py +++ b/src/haven/__init__.py @@ -45,12 +45,9 @@ Monochromator, Robot, ion_chamber, - load_robots, registry, ) -from .devices.dxp import load_dxp_detectors # noqa: F401 from .devices.motor import HavenMotor # noqa: F401 -from .devices.xspress import load_xspress_detectors # noqa: F401 from .energy_ranges import ERange, KRange, merge_ranges # noqa: F401 from .instrument import Instrument # noqa: F401 from .load_instrument import load_instrument # noqa: F401 diff --git a/src/haven/devices/__init__.py b/src/haven/devices/__init__.py index b6a527e2..50144e7b 100644 --- a/src/haven/devices/__init__.py +++ b/src/haven/devices/__init__.py @@ -2,7 +2,7 @@ from .ion_chamber import IonChamber # noqa: F401 from .monochromator import Monochromator # noqa: F401 from .motor import HavenMotor, Motor # noqa: F401 -from .robot import Robot, load_robots # noqa: F401 +from .robot import Robot # noqa: F401 from .table import Table # noqa: F401 # ----------------------------------------------------------------------------- diff --git a/src/haven/devices/aerotech.py b/src/haven/devices/aerotech.py index f96c59e9..5926b285 100644 --- a/src/haven/devices/aerotech.py +++ b/src/haven/devices/aerotech.py @@ -572,35 +572,6 @@ def __init__( super().__init__(name=name) -async def load_aerotech_stages( - config: Mapping = None, - registry: InstrumentRegistry = default_registry, - connect: bool = True, -) -> List[AerotechStage]: - """Load Aerotech XY stages defined in the configuration files' - ``[aerotech_stage]`` sections. - - """ - if config is None: - config = load_config() - devices = [] - for name, stage_data in config.get("aerotech_stage", {}).items(): - mprefix = stage_data["prefix"] - devices.append( - AerotechStage( - name=name, - vertical_prefix=f"{mprefix}{stage_data['pv_vert']}", - horizontal_prefix=f"{mprefix}{stage_data['pv_horiz']}", - delay_prefix=stage_data["delay_prefix"], - ) - ) - if connect: - devices = await connect_devices( - devices, mock=not config["beamline"]["is_connected"], registry=registry - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/aps.py b/src/haven/devices/aps.py index 24469da0..c0a5bf93 100644 --- a/src/haven/devices/aps.py +++ b/src/haven/devices/aps.py @@ -11,6 +11,7 @@ class ApsMachine(ApsMachineParametersDevice): + _ophyd_labels_ = {"synchrotrons"} _default_read_attrs = [ "current", "lifetime", @@ -31,14 +32,6 @@ class ApsMachine(ApsMachineParametersDevice): shutter_status = Cpt(EpicsSignalRO, "RF-ACIS:FePermit:Sect1To35IdM.RVAL") -def load_aps(config=None): - """Load devices related to the synchrotron as a whole.""" - if config is None: - config = load_config() - # Load storage ring device - return make_device(ApsMachine, name="APS", labels={"synchrotrons"}) - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/area_detector.py b/src/haven/devices/area_detector.py index c1a473c9..778f3bfb 100644 --- a/src/haven/devices/area_detector.py +++ b/src/haven/devices/area_detector.py @@ -12,8 +12,10 @@ from ophyd import Component as Cpt from ophyd import DetectorBase as OphydDetectorBase from ophyd import ( + CamBase, Device, EigerDetectorCam, + EpicsSignal, Kind, Lambda750kCam, OphydObject, @@ -32,6 +34,7 @@ ImagePlugin_V31, ImagePlugin_V34, OverlayPlugin, + OverlayPlugin_V34, PvaPlugin_V31, PvaPlugin_V34, ROIPlugin_V31, @@ -42,6 +45,7 @@ from ophyd.areadetector.plugins import TIFFPlugin_V31, TIFFPlugin_V34 from ophyd.flyers import FlyerInterface from ophyd.status import Status, StatusBase, SubscriptionStatus +from ophyd.sim import make_fake_device from .. import exceptions from .._iconfig import load_config @@ -323,7 +327,7 @@ def __init__( try: self.write_path_template = self.write_path_template.format( name=self.parent.name, - root_path=config.get("area_detector", {}).get("root_path", "tmp"), + root_path=config.get("area_detector_root_path", "tmp"), ) except KeyError: warnings.warn(f"Could not format write_path_template {write_path_template}") @@ -502,31 +506,60 @@ class Eiger500K(SingleTrigger, DetectorBase): ] -def load_area_detectors(config=None) -> set: - if config is None: - config = load_config() +class AravisCam(AsyncCamMixin, CamBase): + gain_auto = ADCpt(EpicsSignal, "GainAuto") + acquire_time_auto = ADCpt(EpicsSignal, "ExposureAuto") + + +class AravisDetector(SingleImageModeTrigger, DetectorBase): + """ + A gige-vision camera described by EPICS. + """ + + _default_configuration_attrs = ( + "cam", + "hdf", + "stats1", + "stats2", + "stats3", + "stats4", + ) + _default_read_attrs = ("cam", "hdf", "stats1", "stats2", "stats3", "stats4") + + cam = ADCpt(AravisCam, "cam1:") + image = ADCpt(ImagePlugin_V34, "image1:") + pva = ADCpt(PvaPlugin_V34, "Pva1:") + overlays = ADCpt(OverlayPlugin_V34, "Over1:") + roi1 = ADCpt(ROIPlugin_V34, "ROI1:", kind=Kind.config) + roi2 = ADCpt(ROIPlugin_V34, "ROI2:", kind=Kind.config) + roi3 = ADCpt(ROIPlugin_V34, "ROI3:", kind=Kind.config) + roi4 = ADCpt(ROIPlugin_V34, "ROI4:", kind=Kind.config) + stats1 = ADCpt(StatsPlugin_V34, "Stats1:", kind=Kind.normal) + stats2 = ADCpt(StatsPlugin_V34, "Stats2:", kind=Kind.normal) + stats3 = ADCpt(StatsPlugin_V34, "Stats3:", kind=Kind.normal) + stats4 = ADCpt(StatsPlugin_V34, "Stats4:", kind=Kind.normal) + stats5 = ADCpt(StatsPlugin_V34, "Stats5:", kind=Kind.normal) + hdf = ADCpt(HDF5FilePlugin, "HDF1:", kind=Kind.normal) + # tiff = ADCpt(TIFFFilePlugin, "TIFF1:", kind=Kind.normal) + + +def make_area_detector(prefix:str, name: str, device_class: str, mock=True) -> Device: # Create the area detectors defined in the configuration - devices = [] - for name, adconfig in config.get("area_detector", {}).items(): - try: - DeviceClass = globals().get(adconfig["device_class"]) - except TypeError: - # Not a sub-dictionary, so move on - continue - # Check that it's a valid device class - if DeviceClass is None: - msg = f"area_detector.{name}.device_class={adconfig['device_class']}" - raise exceptions.UnknownDeviceConfiguration(msg) - # Create the device co-routine - devices.append( - make_device( - DeviceClass, - prefix=f"{adconfig['prefix']}:", - name=name, - labels={"area_detectors", "detectors"}, - ) - ) - return devices + try: + DeviceClass = globals().get(device_class) + except TypeError: + msg = f"area_detector.{name}.device_class={device_class}" + raise exceptions.UnknownDeviceConfiguration(msg) + # Create a simulated version if needed + if mock: + DeviceClass = make_fake_device(DeviceClass) + # Create the device co-routine + device = DeviceClass( + prefix=prefix, + name=name, + labels={"area_detectors", "detectors"}, + ) + return device # ----------------------------------------------------------------------------- diff --git a/src/haven/devices/beamline_manager.py b/src/haven/devices/beamline_manager.py index 14a31760..66880d52 100644 --- a/src/haven/devices/beamline_manager.py +++ b/src/haven/devices/beamline_manager.py @@ -78,23 +78,5 @@ def __new__( new_cls = type("BeamlineManager", (cls,), comps) return object.__new__(new_cls) - def __init__(self, *args, iocs={}, **kwargs): - super().__init__(*args, **kwargs) - - -def load_beamline_manager(config=None): - # Load configuration for the beamline manager - if config is None: - config = load_config() - try: - cfg = config["beamline_manager"] - except KeyError: - return - # Set up the beamline manager - return make_device( - BeamlineManager, - prefix=cfg["prefix"], - name=cfg["name"], - labels={"beamline_manager"}, - iocs=cfg["iocs"], - ) + def __init__(self, prefix: str, *, name: str, iocs={}, labels={"beamline_manager"}, **kwargs): + super().__init__(name=name, prefix=prefix, labels=labels, **kwargs) diff --git a/src/haven/devices/camera.py b/src/haven/devices/camera.py deleted file mode 100644 index f0d83c18..00000000 --- a/src/haven/devices/camera.py +++ /dev/null @@ -1,133 +0,0 @@ -import logging -from typing import Sequence - -from ophyd import ADComponent as ADCpt -from ophyd import CamBase, EpicsSignal, Kind -from ophyd.areadetector.plugins import ( - ImagePlugin_V34, - OverlayPlugin_V34, - PvaPlugin_V34, - ROIPlugin_V34, -) - -from .. import exceptions -from .._iconfig import load_config -from ..device import make_device -from .area_detector import ( # noqa: F401 - AsyncCamMixin, - DetectorBase, - HDF5FilePlugin, - SimDetector, - SingleImageModeTrigger, - StatsPlugin_V34, - TIFFFilePlugin, -) - -log = logging.getLogger(__name__) - - -__all__ = ["AravisDetector", "load_cameras"] - - -class AravisCam(AsyncCamMixin, CamBase): - gain_auto = ADCpt(EpicsSignal, "GainAuto") - acquire_time_auto = ADCpt(EpicsSignal, "ExposureAuto") - - -class AravisDetector(SingleImageModeTrigger, DetectorBase): - """ - A gige-vision camera described by EPICS. - """ - - _default_configuration_attrs = ( - "cam", - "hdf", - "stats1", - "stats2", - "stats3", - "stats4", - ) - _default_read_attrs = ("cam", "hdf", "stats1", "stats2", "stats3", "stats4") - - cam = ADCpt(AravisCam, "cam1:") - image = ADCpt(ImagePlugin_V34, "image1:") - pva = ADCpt(PvaPlugin_V34, "Pva1:") - overlays = ADCpt(OverlayPlugin_V34, "Over1:") - roi1 = ADCpt(ROIPlugin_V34, "ROI1:", kind=Kind.config) - roi2 = ADCpt(ROIPlugin_V34, "ROI2:", kind=Kind.config) - roi3 = ADCpt(ROIPlugin_V34, "ROI3:", kind=Kind.config) - roi4 = ADCpt(ROIPlugin_V34, "ROI4:", kind=Kind.config) - stats1 = ADCpt(StatsPlugin_V34, "Stats1:", kind=Kind.normal) - stats2 = ADCpt(StatsPlugin_V34, "Stats2:", kind=Kind.normal) - stats3 = ADCpt(StatsPlugin_V34, "Stats3:", kind=Kind.normal) - stats4 = ADCpt(StatsPlugin_V34, "Stats4:", kind=Kind.normal) - stats5 = ADCpt(StatsPlugin_V34, "Stats5:", kind=Kind.normal) - hdf = ADCpt(HDF5FilePlugin, "HDF1:", kind=Kind.normal) - # tiff = ADCpt(TIFFFilePlugin, "TIFF1:", kind=Kind.normal) - - -def load_cameras(config=None) -> Sequence[DetectorBase]: - """Create co-routines for loading cameras from config files. - - Returns - ======= - coros - A set of co-routines that can be awaited to load the cameras. - - """ - if config is None: - config = load_config() - # Get configuration details for the cameras - device_configs = { - k: v - for (k, v) in config["camera"].items() - if hasattr(v, "keys") and "prefix" in v.keys() - } - # Load each camera - devices = [] - for key, cam_config in device_configs.items(): - class_name = cam_config.get("device_class", "AravisDetector") - camera_name = cam_config.get("name", key) - description = cam_config.get("description", cam_config.get("name", key)) - DeviceClass = globals().get(class_name) - # Check that it's a valid device class - if DeviceClass is None: - msg = f"camera.{key}.device_class={cam_config['device_class']}" - raise exceptions.UnknownDeviceConfiguration(msg) - # Create the device object - devices.append( - make_device( - DeviceClass=DeviceClass, - prefix=f"{cam_config['prefix']}:", - name=camera_name, - description=description, - labels={"cameras", "detectors"}, - ) - ) - return devices - - -# ----------------------------------------------------------------------------- -# :author: Mark Wolfman -# :email: wolfman@anl.gov -# :copyright: Copyright © 2023, UChicago Argonne, LLC -# -# Distributed under the terms of the 3-Clause BSD License -# -# The full license is in the file LICENSE, distributed with this software. -# -# DISCLAIMER -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# ----------------------------------------------------------------------------- diff --git a/src/haven/devices/dxp.py b/src/haven/devices/dxp.py index c11f4a51..6761e5f9 100644 --- a/src/haven/devices/dxp.py +++ b/src/haven/devices/dxp.py @@ -390,7 +390,7 @@ def parse_xmap_buffer(buff): return data -def make_dxp_device(device_name, prefix, num_elements): +def make_dxp_device(name, prefix, num_elements): # Build the mca components mca_range = range(0, num_elements) attrs = { @@ -419,31 +419,6 @@ def make_dxp_device(device_name, prefix, num_elements): ) -def load_dxp_detectors(config=None): - """Load all the DXP-based detector devices. - - Configuration is determined from the iconfig.toml file. - - Optionally, *config* can be given a dictionary with configuration - matching the iconfig.toml file to use instead. Mostly useful for - testing. - - """ - # Get the detector definitions from config files - if config is None: - config = load_config() - devices = [] - for name, cfg in config.get("dxp", {}).items(): - devices.append( - make_dxp_device( - device_name=name, - prefix=cfg["prefix"], - num_elements=cfg["num_elements"], - ) - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/energy_positioner.py b/src/haven/devices/energy_positioner.py index 2009c4a6..c383f3a6 100644 --- a/src/haven/devices/energy_positioner.py +++ b/src/haven/devices/energy_positioner.py @@ -65,12 +65,12 @@ def __init__( self, mono_prefix: str, undulator_prefix: str, - *args, + name: str, **kwargs, ): self.mono_prefix = mono_prefix self.undulator_prefix = undulator_prefix - super().__init__(*args, **kwargs) + super().__init__(name=name, **kwargs) def set_energy(self, *, mds: MultiDerivedSignal, value: float): ev_per_kev = 1000 @@ -121,23 +121,6 @@ def limits(self): ) -def load_energy_positioner(config=None): - # Load PV's from config - if config is None: - config = load_config() - # Guard to make sure we have a mono and ID configuration - if "monochromator" not in config.keys() or "undulator" not in config.keys(): - return - # Make the combined energy device - device = make_device( - EnergyPositioner, - name="energy", - mono_prefix=config["monochromator"]["prefix"], - undulator_prefix=config["undulator"]["prefix"], - ) - return device - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/heater.py b/src/haven/devices/heater.py index 2f610abf..b2a091ef 100644 --- a/src/haven/devices/heater.py +++ b/src/haven/devices/heater.py @@ -5,8 +5,6 @@ from ophyd import Component as Cpt from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, PVPositioner -from .._iconfig import load_config -from ..device import make_device log = logging.getLogger(__name__) @@ -30,19 +28,6 @@ class CapillaryHeater(PTC10PositionerMixin, PVPositioner): output_enable = Cpt(EpicsSignal, "outputEnable", kind="omitted") -def load_heaters(config=None): - if config is None: - config = load_config() - # Load the heaters - devices = [] - for name, cfg in config.get("heater", {}).items(): - Cls = globals().get(cfg["device_class"]) - devices.append( - make_device(Cls, prefix=f"{cfg['prefix']}:", name=name, labels={"heaters"}) - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/lerix.py b/src/haven/devices/lerix.py index e49e94b9..3ae87fe7 100644 --- a/src/haven/devices/lerix.py +++ b/src/haven/devices/lerix.py @@ -182,26 +182,26 @@ class LERIXSpectrometer(Device): # return dev -def load_lerix_spectrometers(config=None): - """Create devices for the LERIX spectrometer.""" - if config is None: - config = load_config() - # Create spectrometers - devices = [] - for name, cfg in config.get("lerix", {}).items(): - rowland = cfg["rowland"] - devices.append( - make_device( - RowlandPositioner, - name=name, - x_motor_pv=rowland["x_motor_pv"], - y_motor_pv=rowland["y_motor_pv"], - z_motor_pv=rowland["z_motor_pv"], - z1_motor_pv=rowland["z1_motor_pv"], - labels={"lerix_spectromoters"}, - ) - ) - return devices +# def load_lerix_spectrometers(config=None): +# """Create devices for the LERIX spectrometer.""" +# if config is None: +# config = load_config() +# # Create spectrometers +# devices = [] +# for name, cfg in config.get("lerix", {}).items(): +# rowland = cfg["rowland"] +# devices.append( +# make_device( +# RowlandPositioner, +# name=name, +# x_motor_pv=rowland["x_motor_pv"], +# y_motor_pv=rowland["y_motor_pv"], +# z_motor_pv=rowland["z_motor_pv"], +# z1_motor_pv=rowland["z1_motor_pv"], +# labels={"lerix_spectromoters"}, +# ) +# ) +# return devices # ----------------------------------------------------------------------------- diff --git a/src/haven/devices/power_supply.py b/src/haven/devices/power_supply.py index 2a635b93..c4816573 100644 --- a/src/haven/devices/power_supply.py +++ b/src/haven/devices/power_supply.py @@ -3,8 +3,6 @@ from ophyd import Device, EpicsSignal, EpicsSignalRO from ophyd import FormattedComponent as FCpt -from .._iconfig import load_config -from ..device import make_device log = logging.getLogger(__name__) @@ -31,30 +29,9 @@ class NHQ203MChannel(Device): ) status = FCpt(EpicsSignalRO, name="status", suffix="{prefix}:ModStatus{ch_num}_rbv") - def __init__(self, prefix: str, ch_num: int, name: str, *args, **kwargs): + def __init__(self, prefix: str, ch_num: int, name: str, labels={"power_supplies"}, *args, **kwargs): self.ch_num = ch_num - super().__init__(prefix=prefix, name=name, *args, **kwargs) - - -def load_power_supplies(config=None): - if config is None: - config = load_config() - # Determine if any power supplies are available - ps_configs = config.get("power_supply", {}) - devices = [] - for name, ps_config in ps_configs.items(): - # Do it once for each channel - for ch_num in range(1, ps_config["n_channels"] + 1): - devices.append( - make_device( - NHQ203MChannel, - name=f"{name}_ch{ch_num}", - prefix=ps_config["prefix"], - ch_num=ch_num, - labels={"power_supplies"}, - ) - ) - return devices + super().__init__(prefix=prefix, name=name, labels=labels, *args, **kwargs) # ----------------------------------------------------------------------------- diff --git a/src/haven/devices/robot.py b/src/haven/devices/robot.py index 6efaa117..9ef02ff7 100644 --- a/src/haven/devices/robot.py +++ b/src/haven/devices/robot.py @@ -67,6 +67,9 @@ class Sample(Device): ry = Cpt(EpicsSignalRO, ":ry") rz = Cpt(EpicsSignalRO, ":rz") + def __init__(self, *args, labels={"robots"}, **kwargs): + super().__init__(*args, labels=labels, **kwargs) + def transfer_samples(num_samples: int): """Create a dictionary with robot sample device definitions. @@ -151,20 +154,6 @@ class Robot(Device): samples = DCpt(transfer_samples(24)) -def load_robots(config=None): - # Load PV's from config - if config is None: - config = load_config() - # Build robot devices - robots = config.get("robot", {}) - devices = [] - for name, cfg in robots.items(): - devices.append( - make_device(Robot, name=name, labels={"robots"}, prefix=cfg["prefix"]) - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Yanna Chen # :email: yannachen@anl.gov diff --git a/src/haven/devices/scaler.py b/src/haven/devices/scaler.py index 2d43fed5..9ba550fa 100644 --- a/src/haven/devices/scaler.py +++ b/src/haven/devices/scaler.py @@ -207,27 +207,6 @@ def __init__(self, prefix, channels: list[int], name=""): super().__init__(name=name) -async def load_scalers( - config=None, connect=True, registry: InstrumentRegistry = default_registry -): - """Load counting scaler devices.""" - if config is None: - config = load_config() - # Create the scaler devices - devices = [] - for name, cfg in config.get("scaler", {}).items(): - channels = range(cfg["num_channels"]) - devices.append( - MultiChannelScaler(prefix=cfg["prefix"], channels=channels, name=name) - ) - # Connect to devices - if connect: - devices = await connect_devices( - devices, mock=not config["beamline"]["is_connected"], registry=registry - ) - return - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/shutter.py b/src/haven/devices/shutter.py index 1a3b696a..df81218b 100644 --- a/src/haven/devices/shutter.py +++ b/src/haven/devices/shutter.py @@ -32,11 +32,11 @@ class PssShutter(PVPositionerIsClose): allow_close: bool def __init__( - self, *args, allow_open: bool = True, allow_close: bool = True, **kwargs + self, prefix: str, name: str, allow_open: bool = True, allow_close: bool = True, labels={"shutters"}, **kwargs ): self.allow_open = allow_open self.allow_close = allow_close - super().__init__(*args, **kwargs) + super().__init__(prefix=prefix, name=name, labels=labels, **kwargs) def check_value(self, pos): """Check that the shutter has the right permissions.""" @@ -84,29 +84,6 @@ def _shutter_setpoint(self, mds: MultiDerivedSignal, items: SignalToValue) -> in close_signal = Cpt(EpicsSignal, "CloseEPICSC", kind="omitted") -def load_shutters(config=None): - if config is None: - config = load_config() - # Guard to make sure there's at least one shutter configuration - if "shutter" not in config.keys(): - return [] - # Load the shutter configurations into devices - devices = [] - for name, d in config["shutter"].items(): - # Calculate suitable PV values - devices.append( - make_device( - PssShutter, - prefix=d["prefix"], - allow_open=d.get("allow_open", True), - allow_close=d.get("allow_close", True), - name=name, - labels={"shutters"}, - ) - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/slits.py b/src/haven/devices/slits.py index e210b9be..6cdf41a0 100644 --- a/src/haven/devices/slits.py +++ b/src/haven/devices/slits.py @@ -100,6 +100,7 @@ def __init__( yaw_motor: str, horizontal_motor: str, diagonal_motor: str, + labels={"slits"}, **kwargs, ): # Determine the prefix for the motors @@ -132,47 +133,6 @@ class SlitAxis(Device): ) -def load_slits(config=None): - if config is None: - config = load_config() - # Create slits - devices = [] - for name, slit_config in config.get("slits", {}).items(): - DeviceClass = globals().get(slit_config["device_class"]) - prefix = slit_config["prefix"] - # Check that it's a valid device class - if DeviceClass is None: - msg = f"slits.{name}.device_class={slit_config['device_class']}" - raise exceptions.UnknownDeviceConfiguration(msg) - ioc_prefix = prefix.split(":")[0] - # Determine real motor PVs - motors = {} - if DeviceClass is ApertureSlits: - try: - motors.update( - dict( - horizontal_motor=slit_config["horizontal_motor"], - diagonal_motor=slit_config["diagonal_motor"], - pitch_motor=slit_config["pitch_motor"], - yaw_motor=slit_config["yaw_motor"], - ) - ) - except KeyError: - msg = f"Missing motors for slits.{name}.device_class={slit_config['device_class']}" - raise exceptions.UnknownDeviceConfiguration(msg) - # Create the device - devices.append( - make_device( - DeviceClass, - prefix=prefix, - name=name, - labels={"slits"}, - **motors, - ) - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/stage.py b/src/haven/devices/stage.py index bcd070cc..c4427ee0 100644 --- a/src/haven/devices/stage.py +++ b/src/haven/devices/stage.py @@ -43,34 +43,6 @@ def __init__( super().__init__(name=name) -async def load_stages( - config: Mapping = None, - registry: InstrumentRegistry = default_registry, - connect: bool = True, -): - """Load the stages defined in the configuration files' ``[stage]`` - sections. - - """ - if config is None: - config = load_config() - devices = [] - for name, stage_data in config.get("stage", {}).items(): - prefix = stage_data["prefix"] - devices.append( - XYStage( - name=name, - vertical_prefix=f"{prefix}{stage_data['pv_vert']}", - horizontal_prefix=f"{prefix}{stage_data['pv_horiz']}", - ) - ) - if connect: - devices = await connect_devices( - devices, mock=not config["beamline"]["is_connected"], registry=registry - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/devices/xia_pfcu.py b/src/haven/devices/xia_pfcu.py index be365caf..0990277e 100644 --- a/src/haven/devices/xia_pfcu.py +++ b/src/haven/devices/xia_pfcu.py @@ -6,6 +6,7 @@ """ from enum import IntEnum +from typing import Sequence from ophyd import Component as Cpt from ophyd import DynamicDeviceComponent as DCpt @@ -170,41 +171,13 @@ def __new__(cls, *args, shutters=[], **kwargs): def __init__( self, - *args, - shutters=[], + prefix: str, + name: str, + shutters: Sequence = [], + labels: str={"filter_banks"}, **kwargs, ): - super().__init__(*args, **kwargs) - - -def load_xia_pfcu4s(config=None): - if config is None: - config = load_config() - # Read the filter bank configurations from the config file - devices = [] - for name, cfg in config.get("pfcu4", {}).items(): - try: - prefix = cfg["prefix"] - shutters = cfg.get("shutters", []) - except KeyError as ex: - raise exceptions.UnknownDeviceConfiguration( - f"Device {name} missing '{ex.args[0]}': {cfg}" - ) from ex - # Make the device - devices.append( - make_device( - PFCUFilterBank, - prefix=prefix, - name=name, - shutters=shutters, - labels={"filter_banks"}, - ) - ) - return devices - - -# def load_xia_pfcu4s(config=None): -# asyncio.run(aload_devices(*load_xia_pfcu4_coros(config=config))) + super().__init__(prefix=prefix, name=name, labels=labels, **kwargs) # ----------------------------------------------------------------------------- diff --git a/src/haven/devices/xspress.py b/src/haven/devices/xspress.py index cf0b4842..e1b4617a 100644 --- a/src/haven/devices/xspress.py +++ b/src/haven/devices/xspress.py @@ -550,29 +550,6 @@ def make_xspress_device(name, prefix, num_elements): ) -def load_xspress_detectors(config=None): - """Load all the xspress-based detector devices. - - Configuration is determined from the iconfig.toml file. - - Optionally, *config* can be given a dictionary with configuration - matching the iconfig.toml file to use instead. Mostly useful for - testing. - - """ - if config is None: - config = load_config() - # Create detector device - devices = [] - for name, cfg in config.get("xspress", {}).items(): - devices.append( - make_xspress_device( - prefix=cfg["prefix"], num_elements=cfg["num_elements"], name=name - ) - ) - return devices - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/iconfig_testing.toml b/src/haven/iconfig_testing.toml index 4eadb856..08f7e9fa 100644 --- a/src/haven/iconfig_testing.toml +++ b/src/haven/iconfig_testing.toml @@ -6,10 +6,8 @@ name = "SPC Beamline (sector unknown)" # If not, we will provide mocked devices. hardware_is_present = false -[facility] - -name = "Advanced Photon Source" -xray_source = "insertion device" +[[ synchrotron ]] +name = "advanced_photon_source" [database.databroker] @@ -47,28 +45,35 @@ redis_addr = "localhost:6379" uri = "http://localhost:8337/" entry_node = "255id_testing" -[shutter.front_end_shutter] +################ +# PSS Shutters # +################ +# +# Each PSS shutter has optional arguments *allow_open* and +# *allow_close*. These determine whether Ophyd will allow the shutter +# to open and close, but has no relationship to EPICS permissions. -hutch = "A" +[[ pss_shutter ]] +name = "front_end_shutter" prefix = "S255ID-PSS:FES:" -allow_open = true # Default allow_close = false +# allow_open = true # Default -[shutter.hutch_shutter] -hutch = "C" +[[ pss_shutter ]] +name = "hutch_shutter" prefix = "S255ID-PSS:SCS:" -allow_open = true # Default -allow_close = true # Default +# allow_open = true # Default +# allow_close = true # Default -[undulator] -prefix = "id_ioc:" +# Energy Positioner +# ================= -[monochromator] +[[ energy ]] +monochromator_prefix = "mono_ioc:" +undulator_prefix = "id_ioc:" -prefix = "mono_ioc:" -ioc_branch = "UP" # For caQtDM macros # Ion chambers # ============ @@ -80,7 +85,6 @@ ioc_branch = "UP" # For caQtDM macros # updated based on the .DESC PV for the scaler channel. [[ ion_chamber ]] - scaler_prefix = "255idcVME:3820:" scaler_channel = 2 preamp_prefix = "255idc:SR03:" @@ -90,16 +94,16 @@ voltmeter_channel = 1 counts_per_volt_second = 10e6 name = "I0" + # Scalers # ======= # -# Each scaler will have a section similar to -# [scaler.vme_scaler_1] -# -# where the second segment is the name for the scaler. - -[scaler.vme_scaler_1] +# These definitions are not for using ion chambers, but for if the +# scaler is needed as an independent device. The ion chamber +# defintions include a scaler channel. +[[ scaler ]] +name = "scaler_1" prefix = "255idcVME:3820:" num_channels = 32 @@ -128,40 +132,45 @@ prefix = "255idcVME:m1" # auto_name = None -# Keys for camera definitions must begin with "cam" (e.g. "camA", "camB") -[camera.camA] - -name = "s25id-gige-A" -description = "GigE Vision A" -prefix = "255idgigeA" - -[aerotech_stage.aerotech] - -prefix = "255idc" -delay_prefix = "255idc:DG645" -pv_vert = ":m1" -pv_horiz = ":m2" - -[stage.sample_stage] +# Sample stages +# ============= +[[ xy_stage ]] prefix = "255idcVME:" pv_vert = "m13" pv_horiz = "m14" -[power_supply.NHQ01] +# Aerotech controller support disabled until new controllers are ready +# [aerotech_stage.aerotech] +# prefix = "255idc" +# delay_prefix = "255idc:DG645" +# pv_vert = ":m1" +# pv_horiz = ":m2" + + +# External high-voltage power supplies +# ==================================== +[[ power_supply ]] +# An NHQ203M power supply +name = "NHQ01" prefix = "ps_ioc:NHQ01" -n_channels = 2 +ch_num = 1 + + +# Slits +# ===== [[ blade_slits ]] +# A set of 4 slits, two for each direction name = "KB_slits" prefix = "vme_crate_ioc:KB" -[slits.whitebeam_slits] -# A single rotating aperture slit, like the 25-ID white/pinkbeam slits +[[ aperture_slits ]] +# A single rotating aperture slit, like the 25-ID whitebeam slits +name = "whitebeam_slits" prefix = "255ida:slits:US:" -device_class = "ApertureSlits" pitch_motor = "m33" yaw_motor = "m34" horizontal_motor = "m35" @@ -203,6 +212,7 @@ name = "ORM2" prefix = "25ida:ORM2:" bendable = true + # Table # ===== # An optical table with a specific configuration of motors @@ -223,62 +233,80 @@ prefix = "255idcVME:" vertical_motor = "m26" horizontal_motor = "m25" -[area_detector] -root_path = "tmp" # Omit leading slash, will get added by ophyd -[area_detector.sim_det] +# Area detectors +# ============== +# +# Area detectors includes gigE vision cameras. + +area_detector_root_path = "tmp" -prefix = "255idSimDet" +[[ area_detector ]] +name = "sim_det" +prefix = "255idSimDet:" device_class = "SimDetector" -[lerix.lerix.rowland] +# [lerix.lerix.rowland] + +# x_motor_pv = "255idVME:m1" +# y_motor_pv = "255idVME:m2" +# z_motor_pv = "255idVME:m3" +# z1_motor_pv = "255idVME:m4" -x_motor_pv = "255idVME:m1" -y_motor_pv = "255idVME:m2" -z_motor_pv = "255idVME:m3" -z1_motor_pv = "255idVME:m4" -[heater.capillary_heater] +# Heaters and Furnaces +# ==================== -prefix = "255idptc10" -device_class = "CapillaryHeater" +[[ capillary_heater ]] +name = "capillary_heater" +prefix = "255idptc10:" -[robot.A] + +# Robots +# ====== + +[[ robot ]] +name="austin" prefix = "255idAustin" + # Managed IOC control PVs -[beamline_manager] +# ======================= +[[ beamline_manager ]] name = "GLaDOS" prefix = "255idc:glados:" beamline = "25-ID-C" +iocs = {ioc255idb = "ioc255idb:", ioc255idc = "ioc255idc:"} -[beamline_manager.iocs] -ioc255idb = "ioc255idb:" -ioc255idc = "ioc255idc:" -[fluorescence_detector] - -[dxp.vortex_me4] +# Fluorescence Detectors +# ====================== -prefix = "vortex_me4" +[[ dxp ]] +name = "vortex_me4" +prefix = "vortex_me4:" num_elements = 4 -[dxp.canberra_Ge7] - -prefix = "20xmap8" +[[ dxp ]] +name = "canberra_Ge7" +prefix = "20xmap8:" num_elements = 4 -[xspress.vortex_me4_xsp] - -prefix = "vortex_me4_xsp" +[[ xspress ]] +name = "vortex_me4_xsp" +prefix = "vortex_me4_xsp:" num_elements = 4 -[pfcu4.filter_bank0] -prefix = "255idc:pfcu0:" +# Filter boxes +# ============ -[pfcu4.filter_bank1] +[[ pfcu4 ]] +name = "filter_bank0" +prefix = "255idc:pfcu0:" +[[ pfcu4 ]] +name = "filter_bank1" prefix = "255idc:pfcu1:" shutters = [[3, 4]] diff --git a/src/haven/load_instrument.py b/src/haven/load_instrument.py index 0163e578..68778e3a 100644 --- a/src/haven/load_instrument.py +++ b/src/haven/load_instrument.py @@ -7,27 +7,26 @@ from ._iconfig import load_config from .devices.aerotech import AerotechStage -from .devices.aps import load_aps -from .devices.area_detector import load_area_detectors -from .devices.beamline_manager import load_beamline_manager -from .devices.camera import load_cameras -from .devices.dxp import load_dxp_detectors -from .devices.energy_positioner import load_energy_positioner -from .devices.heater import load_heaters +from .devices.aps import ApsMachine +from .devices.area_detector import make_area_detector +from .devices.beamline_manager import BeamlineManager +from .devices.dxp import make_dxp_device +from .devices.energy_positioner import EnergyPositioner +from .devices.heater import CapillaryHeater from .devices.instrument_registry import InstrumentRegistry from .devices.instrument_registry import registry as default_registry from .devices.ion_chamber import IonChamber -from .devices.lerix import load_lerix_spectrometers from .devices.mirrors import HighHeatLoadMirror, KBMirrors from .devices.motor import Motor, load_motors -from .devices.power_supply import load_power_supplies -from .devices.robot import load_robots -from .devices.shutter import load_shutters -from .devices.slits import load_slits +from .devices.power_supply import NHQ203MChannel +from .devices.robot import Robot +from .devices.scaler import Scaler +from .devices.shutter import PssShutter +from .devices.slits import BladeSlits, ApertureSlits from .devices.stage import XYStage from .devices.table import Table -from .devices.xia_pfcu import load_xia_pfcu4s -from .devices.xspress import load_xspress_detectors +from .devices.xia_pfcu import PFCUFilterBank +from .devices.xspress import make_xspress_device from .instrument import Instrument __all__ = ["load_instrument"] @@ -74,6 +73,20 @@ async def load_instrument( "table": Table, "aerotech_stage": AerotechStage, "motor": Motor, + "blade_slits": BladeSlits, + "aperture_slits": ApertureSlits, + "capillary_heater": CapillaryHeater, + "power_supply": NHQ203MChannel, + "synchrotron": ApsMachine, + "robot": Robot, + "pfcu4": PFCUFilterBank, + "pss_shutter": PssShutter, + "energy": EnergyPositioner, + "xspress": make_xspress_device, + "dxp": make_dxp_device, + "beamline_manager": BeamlineManager, + "area_detector": make_area_detector, + "scaler": Scaler, }, ) t0 = time.monotonic() @@ -87,47 +100,6 @@ async def load_instrument( f"Loaded [repr.number]{len(instrument.devices)}[/] devices in {load_time:.1f} sec.", flush=True, ) - # Clear out any existing registry entries - registry = instrument.registry - # Load the configuration - if config is None: - config = load_config() - # Load the devices from the configuration files - # Synchronous (threaded) devices - devices = [] - devices.extend( - [ - load_aps(config=config), - *load_area_detectors(config=config), - load_beamline_manager(config=config), - *load_cameras(config=config), - *load_dxp_detectors(config=config), - load_energy_positioner(config=config), - *load_heaters(config=config), - *load_lerix_spectrometers(config=config), - *load_power_supplies(config=config), - *load_robots(config=config), - *load_shutters(config=config), - *load_slits(config=config), - *load_xia_pfcu4s(config=config), - *load_xspress_detectors(config=config), - ] - ) - # Filter out devices that couldn't be reached - devices = [d for d in devices if d is not None] - # Put the devices into the registry - if not getattr(registry, "auto_register", True): - [registry.register(device) for device in devices] - # Only keep connected devices - disconnected = [] - if wait_for_connection and registry is not None: - disconnected = registry.pop_disconnected(timeout=timeout) - devices = [dev for dev in devices if dev not in disconnected] - if len(disconnected) > 0: - msg = "Removed disconnected devices: " - msg += ", ".join(dev.name for dev in disconnected) - warnings.warn(msg) - log.warning(msg) # Return the final list if return_devices: return devices diff --git a/src/haven/tests/test_aerotech.py b/src/haven/tests/test_aerotech.py index 46a8a7ce..2a08fbfb 100644 --- a/src/haven/tests/test_aerotech.py +++ b/src/haven/tests/test_aerotech.py @@ -9,7 +9,6 @@ from haven.devices.aerotech import ( AerotechMotor, AerotechStage, - load_aerotech_stages, ureg, ) @@ -31,15 +30,6 @@ def aerotech_axis(aerotech): yield m -async def test_load_aerotech_stage(sim_registry): - await load_aerotech_stages() - # Make sure these are findable - stage_ = sim_registry.find(name="aerotech") - assert stage_ is not None - vert_ = sim_registry.find(name="aerotech-vert") - assert vert_ is not None - - def test_aerotech_flyer(sim_registry): aeroflyer = AerotechMotor( prefix="255idc:m1", name="aerotech_flyer", axis="@0", encoder=6 diff --git a/src/haven/tests/test_aps.py b/src/haven/tests/test_aps.py index 095d6aaf..dce006c5 100644 --- a/src/haven/tests/test_aps.py +++ b/src/haven/tests/test_aps.py @@ -1,14 +1,8 @@ -from haven.devices import aps - - -def test_load_aps(sim_registry): - aps.load_aps() - aps_ = sim_registry.find(name="APS") - assert hasattr(aps_, "current") +from haven.devices.aps import ApsMachine def test_read_attrs(): - device = aps.ApsMachine(name="Aps") + device = ApsMachine(name="Aps") read_attrs = ["current", "lifetime"] for attr in read_attrs: assert attr in device.read_attrs diff --git a/src/haven/tests/test_area_detector.py b/src/haven/tests/test_area_detector.py index f04f109a..b41f7e07 100644 --- a/src/haven/tests/test_area_detector.py +++ b/src/haven/tests/test_area_detector.py @@ -10,7 +10,7 @@ DetectorBase, DetectorState, HDF5FilePlugin, - load_area_detectors, + make_area_detector, ) @@ -41,8 +41,8 @@ def test_flyscan_kickoff(detector): assert event[0].timestamp == pytest.approx(time.time()) -def test_load_area_detectors(sim_registry): - load_area_detectors() +def test_make_area_detector(sim_registry): + ad = make_area_detector(name="ad_sim", prefix="255idADSimDet:", device_class="SimDetector", mock=True) # Check that some area detectors were loaded dets = sim_registry.findall(label="area_detectors") diff --git a/src/haven/tests/test_beamline_manager.py b/src/haven/tests/test_beamline_manager.py index be03530d..088d9c68 100644 --- a/src/haven/tests/test_beamline_manager.py +++ b/src/haven/tests/test_beamline_manager.py @@ -1,4 +1,4 @@ -from haven.devices.beamline_manager import BeamlineManager, load_beamline_manager +from haven.devices.beamline_manager import BeamlineManager """ glados:alive.VAL @@ -265,11 +265,6 @@ """ -def test_load_manager(): - """Check that the manager object is created from configuration file.""" - load_beamline_manager() - - def test_iocs(): """Check that the managed IOCs are available.""" iocs = { diff --git a/src/haven/tests/test_camera.py b/src/haven/tests/test_camera.py deleted file mode 100644 index ab6e6051..00000000 --- a/src/haven/tests/test_camera.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest -from ophyd import DetectorBase -from ophyd.sim import instantiate_fake_device - -from haven import load_config, registry -from haven.devices.camera import AravisDetector, load_cameras - -PREFIX = "255idgigeA:" - - -def test_load_cameras(sim_registry): - load_cameras(config=load_config()) - # Check that cameras were registered - cameras = list(registry.findall(label="cameras")) - assert len(cameras) == 1 - assert isinstance(cameras[0], DetectorBase) - - -@pytest.fixture() -def camera(sim_registry): - camera = instantiate_fake_device( - AravisDetector, prefix="255idgigeA:", name="camera" - ) - return camera - - -def test_camera_device(camera): - assert isinstance(camera, DetectorBase) - assert hasattr(camera, "cam") - - -def test_camera_in_registry(sim_registry, camera): - # Check that all sub-components are accessible - sim_registry.find(f"{camera.name}_cam") - sim_registry.find(f"{camera.name}_cam.gain") - - -def test_default_time_signal(camera): - assert camera.default_time_signal is camera.cam.acquire_time - - -def test_hdf5_write_path(camera): - # The HDF5 file should get dynamically re-written based on config file - assert camera.hdf.write_path_template == "/tmp/%Y/%m/camera" - - -# ----------------------------------------------------------------------------- -# :author: Mark Wolfman -# :email: wolfman@anl.gov -# :copyright: Copyright © 2023, UChicago Argonne, LLC -# -# Distributed under the terms of the 3-Clause BSD License -# -# The full license is in the file LICENSE, distributed with this software. -# -# DISCLAIMER -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# ----------------------------------------------------------------------------- diff --git a/src/haven/tests/test_energy_positioner.py b/src/haven/tests/test_energy_positioner.py index 8543f787..4d123cef 100644 --- a/src/haven/tests/test_energy_positioner.py +++ b/src/haven/tests/test_energy_positioner.py @@ -2,7 +2,7 @@ from ophyd import PVPositioner from ophyd.sim import instantiate_fake_device -from haven.devices.energy_positioner import EnergyPositioner, load_energy_positioner +from haven.devices.energy_positioner import EnergyPositioner @pytest.fixture() @@ -28,14 +28,6 @@ def test_set_energy(positioner): assert positioner.undulator.energy.get().setpoint == 10.150 -def test_load_energy_positioner(sim_registry): - load_energy_positioner() - energy = sim_registry["energy"] - assert isinstance(energy, PVPositioner) - assert hasattr(energy, "monochromator") - assert hasattr(energy, "undulator") - - def test_real_to_pseudo_positioner(positioner): positioner.monochromator.energy.user_readback._readback = 5000.0 # Check that the pseudo single is updated diff --git a/src/haven/tests/test_fluorescence_detectors.py b/src/haven/tests/test_fluorescence_detectors.py index f0488e5e..dea23a67 100644 --- a/src/haven/tests/test_fluorescence_detectors.py +++ b/src/haven/tests/test_fluorescence_detectors.py @@ -14,8 +14,7 @@ import pytest from ophyd import OphydObject, Signal -from haven.devices.dxp import load_dxp_detectors, parse_xmap_buffer -from haven.devices.xspress import load_xspress_detectors +from haven.devices.dxp import parse_xmap_buffer DETECTORS = ["dxp", "xspress"] # DETECTORS = ['dxp'] @@ -32,29 +31,6 @@ def vortex(request): yield det -def test_load_xspress_detectors(sim_registry, mocker): - load_xspress_detectors(config=None) - vortex = sim_registry.find(name="vortex_me4_xsp") - assert vortex.mcas.component_names == ("mca0", "mca1", "mca2", "mca3") - - -def test_load_dxp_detectors(sim_registry): - load_dxp_detectors(config=None) - # See if the device was loaded - vortex = sim_registry.find(name="vortex_me4") - # Check that the MCA's are available - assert hasattr(vortex.mcas, "mca0") - assert hasattr(vortex.mcas, "mca1") - assert hasattr(vortex.mcas, "mca2") - assert hasattr(vortex.mcas, "mca3") - # Check that MCA's have ROI's available - assert hasattr(vortex.mcas.mca1, "rois") - assert hasattr(vortex.mcas.mca1.rois, "roi0") - # Check that bluesky hints were added - assert hasattr(vortex.mcas.mca1.rois.roi0, "use") - # assert vortex.mcas.mca1.rois.roi1.is_hinted.pvname == "vortex_me4:mca1_R1BH" - - # @pytest.mark.parametrize("vortex", ["xspress"], indirect=True) def test_acquire_frames_xspress(xspress): """Can we acquire a single frame using the dedicated signal.""" diff --git a/src/haven/tests/test_heater.py b/src/haven/tests/test_heater.py index 75dbf1a8..b501085b 100644 --- a/src/haven/tests/test_heater.py +++ b/src/haven/tests/test_heater.py @@ -1,15 +1,6 @@ -from haven.devices.heater import load_heaters - PREFIX = "255idptc10:" -def test_load_heaters(): - heaters = load_heaters() - assert len(heaters) == 1 - heater = heaters[0] - assert heaters[0].name == "capillary_heater" - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/tests/test_iconfig.py b/src/haven/tests/test_iconfig.py index 66624bdd..0028d2a2 100644 --- a/src/haven/tests/test_iconfig.py +++ b/src/haven/tests/test_iconfig.py @@ -43,15 +43,16 @@ def test_merging_dicts(): ] test_file = this_dir / "test_iconfig.toml" config = load_config(file_paths=(*default_files, test_file)) - assert "description" in config["camera"]["camA"].keys() + print(config) + assert "prefix" in config["area_detector"][0].keys() def test_haven_config_cli(capsys): """Test the function used as a CLI way to get config values.""" - print_config_value(["monochromator.prefix"]) + print_config_value(["xray_source.prefix"]) # Check stdout for config value captured = capsys.readouterr() - assert captured.out == "mono_ioc:\n" + assert captured.out == "ID255ds:\n" def test_beamline_connected(): diff --git a/src/haven/tests/test_instrument.py b/src/haven/tests/test_instrument.py index e94a272d..ffc20f28 100644 --- a/src/haven/tests/test_instrument.py +++ b/src/haven/tests/test_instrument.py @@ -96,7 +96,7 @@ async def test_load(monkeypatch): # Mock out the relevant methods to test monkeypatch.setattr(instrument, "parse_toml_file", MagicMock()) monkeypatch.setattr(instrument, "connect", AsyncMock()) - monkeypatch.setenv("HAVEN_CONFIG_FILES", toml_file, prepend=False) + monkeypatch.setenv("HAVEN_CONFIG_FILES", str(toml_file), prepend=False) # Execute the loading step await instrument.load() # Check that the right methods were called diff --git a/src/haven/tests/test_lerix.py b/src/haven/tests/test_lerix.py index c71ba634..d7336c97 100644 --- a/src/haven/tests/test_lerix.py +++ b/src/haven/tests/test_lerix.py @@ -141,16 +141,6 @@ def test_rowland_circle_component(): assert result.z1.user_setpoint == pytest.approx(1.5308084989341912e-14 * um_per_mm) -def test_load_lerix_spectrometers(sim_registry): - lerix.load_lerix_spectrometers() - device = sim_registry.find(name="lerix") - assert device.name == "lerix" - assert device.x.prefix == "255idVME:m1" - assert device.y.prefix == "255idVME:m2" - assert device.z.prefix == "255idVME:m3" - assert device.z1.prefix == "255idVME:m4" - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/tests/test_power_supply.py b/src/haven/tests/test_power_supply.py index 3521cb5c..39a513b1 100644 --- a/src/haven/tests/test_power_supply.py +++ b/src/haven/tests/test_power_supply.py @@ -1,13 +1,4 @@ -from haven.devices import power_supply - - -def test_load_power_supplies(sim_registry): - power_supply.load_power_supplies() - # Test that the device has the right configuration - devices = list(sim_registry.findall(label="power_supplies")) - assert len(devices) == 2 # 2 channels on the device - device = devices[0] - assert "NHQ01_ch" in device.name +"""Need to write some tests?""" # ----------------------------------------------------------------------------- diff --git a/src/haven/tests/test_robot.py b/src/haven/tests/test_robot.py index e834f01c..ee8bf9c3 100644 --- a/src/haven/tests/test_robot.py +++ b/src/haven/tests/test_robot.py @@ -1,7 +1,7 @@ import pytest from ophydregistry.exceptions import ComponentNotFound -from haven.devices import Robot, load_robots +from haven.devices import Robot def test_robot(): @@ -12,26 +12,6 @@ def test_robot(): assert robot.samples.sample8.rz.pvname == "25idAustin:sample8:rz" -def test_load_robot(sim_registry): - load_robots() - # Test the robot info is extracted properly - rbt = sim_registry.find(label="robots") - assert rbt.name == "A" - assert rbt.prefix == "255idAustin" - - -def test_load_no_robot(sim_registry): - load_robots(config={}) - - # Test the robot is not in config - result = pytest.raises(ComponentNotFound, sim_registry.findall, label="robots") - - # Assert that the expected exception is raised - assert 'Could not find components matching: label="robots", name="None"' in str( - result.value - ) - - # ----------------------------------------------------------------------------- # :author: Yanna Chen # :email: yannachen@anl.gov diff --git a/src/haven/tests/test_run_engine.py b/src/haven/tests/test_run_engine.py index 82c3b012..5eed4e74 100644 --- a/src/haven/tests/test_run_engine.py +++ b/src/haven/tests/test_run_engine.py @@ -1,13 +1,20 @@ import gc +import pytest import databroker from bluesky import RunEngine +from ophyd.sim import instantiate_fake_device from haven import run_engine -from haven.devices.aps import load_aps +from haven.devices.aps import ApsMachine -def test_subscribers_garbage_collection(monkeypatch, sim_registry): +@pytest.fixture() +def aps(sim_registry): + aps = instantiate_fake_device(ApsMachine, name="advanced_photon_source") + + +def test_subscribers_garbage_collection(monkeypatch, aps): """Tests for regression of a bug in databroker. Since databroker uses a weak reference to the insert function, it @@ -16,21 +23,18 @@ def test_subscribers_garbage_collection(monkeypatch, sim_registry): """ monkeypatch.setattr(databroker, "catalog", {"bluesky": databroker.temp()}) - load_aps() RE = run_engine(use_bec=False) assert len(RE.dispatcher.cb_registry.callbacks) == 12 gc.collect() assert len(RE.dispatcher.cb_registry.callbacks) == 12 -def test_run_engine_preprocessors(sim_registry): - load_aps() +def test_run_engine_preprocessors(aps): RE = run_engine(use_bec=False) assert len(RE.preprocessors) > 0 -def test_run_engine_created(sim_registry): - load_aps() +def test_run_engine_created(aps): RE = run_engine(use_bec=False) assert isinstance(RE, RunEngine) diff --git a/src/haven/tests/test_scaler.py b/src/haven/tests/test_scaler.py index 1148de42..7dae2c54 100644 --- a/src/haven/tests/test_scaler.py +++ b/src/haven/tests/test_scaler.py @@ -1,6 +1,6 @@ import pytest -from haven.devices.scaler import MultiChannelScaler, load_scalers +from haven.devices.scaler import MultiChannelScaler @pytest.fixture() @@ -132,13 +132,3 @@ def test_scaler_channel_signals(mcs): assert channel.raw_count.source == "ca://255idcVME:3820:scaler1.S16" assert channel.net_count.source == "ca://255idcVME:3820:scaler1_netB.D" assert channel.offset_rate.source == "ca://255idcVME:3820:scaler1_offset3.D" - - -@pytest.mark.asyncio -async def test_load_scalers(sim_registry, mocker): - await load_scalers(registry=sim_registry) - mcs = sim_registry.find(label="scalers") - assert mcs.name == "vme_scaler_1" - # Check that the channels are set properly - assert hasattr(mcs.scaler, "channels") - assert len(mcs.scaler.channels) == 32 diff --git a/src/haven/tests/test_shutter.py b/src/haven/tests/test_shutter.py index cbb11831..c87bfe7b 100644 --- a/src/haven/tests/test_shutter.py +++ b/src/haven/tests/test_shutter.py @@ -2,7 +2,7 @@ from ophyd import sim from ophyd.utils.errors import ReadOnlyError -from haven.devices.shutter import PssShutter, ShutterState, load_shutters +from haven.devices.shutter import PssShutter, ShutterState @pytest.fixture() @@ -11,26 +11,6 @@ def shutter(sim_registry): return shutter -def test_load_shutters(sim_registry, beamline_connected): - load_shutters() - shutters = list(sim_registry.findall(label="shutters")) - assert len(shutters) == 2 - # Check Shutter A PVs - shutterA = sim_registry.find(name="front_end_shutter") - assert shutterA.name == "front_end_shutter" - assert shutterA.open_signal.pvname == "S255ID-PSS:FES:OpenEPICSC" - assert shutterA.close_signal.pvname == "S255ID-PSS:FES:CloseEPICSC" - assert shutterA.readback.pvname == "S255ID-PSS:FES:BeamBlockingM.VAL" - # Check that Shutter A is read-only - assert not shutterA.allow_close - # Check Shutter C PVs - shutterC = sim_registry.find(name="hutch_shutter") - assert shutterC.name == "hutch_shutter" - assert shutterC.open_signal.pvname == "S255ID-PSS:SCS:OpenEPICSC" - assert shutterC.close_signal.pvname == "S255ID-PSS:SCS:CloseEPICSC" - assert shutterC.readback.pvname == "S255ID-PSS:SCS:BeamBlockingM.VAL" - - def test_shutter_setpoint(shutter): """When we open and close the shutter, do the right EPICS signals get set? diff --git a/src/haven/tests/test_slits.py b/src/haven/tests/test_slits.py index 994f0976..34f3586b 100644 --- a/src/haven/tests/test_slits.py +++ b/src/haven/tests/test_slits.py @@ -15,20 +15,6 @@ def test_slits_tweak(): assert slits_obj.v.center.tweak_forward.pvname == "255idc:KB_slitsVcenter_tweak.B" -def test_load_slits(sim_registry, monkeypatch): - slits.load_slits() - # Check that the slits were loaded - devices = sim_registry.findall(label="slits") - assert len(devices) == 2 - KB_slits = sim_registry.find(name="KB_slits") - assert KB_slits.prefix == "vme_crate_ioc:KB" - whitebeam_slits = sim_registry.find(name="whitebeam_slits") - assert whitebeam_slits.prefix == "255ida:slits:US:" - # Check that the right slits subclasses were used - assert isinstance(KB_slits, slits.BladeSlits) - assert isinstance(whitebeam_slits, slits.ApertureSlits) - - def test_aperture_PVs(): aperture = slits.ApertureSlits( "255ida:slits:US:", diff --git a/src/haven/tests/test_stages.py b/src/haven/tests/test_stages.py index 5bce93fa..0ef7b34e 100644 --- a/src/haven/tests/test_stages.py +++ b/src/haven/tests/test_stages.py @@ -16,12 +16,6 @@ def test_stage_init(sim_registry): assert len(list(sim_registry.findall(label="stages"))) == 1 -async def test_load_stages(sim_registry): - stages = await stage.load_stages() - assert len(stages) == 1 - assert isinstance(stages[0], stage.XYStage) - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/haven/tests/test_xia_pfcu.py b/src/haven/tests/test_xia_pfcu.py index b0dc5edc..fd5f47ac 100644 --- a/src/haven/tests/test_xia_pfcu.py +++ b/src/haven/tests/test_xia_pfcu.py @@ -8,7 +8,6 @@ PFCUFilterBank, PFCUShutter, ShutterState, - load_xia_pfcu4s, ) @@ -81,34 +80,6 @@ def test_pfcu_shutter_close(shutter_bank): assert shutter_bank.setpoint.get() == 0b0101 -def test_load_filters(monkeypatch): - # Simulate the function for making the device - # works around a bug due to the use of __new__ to make a factory - device_maker = mock.MagicMock() - monkeypatch.setattr(xia_pfcu, "make_device", device_maker) - # Call the code under test - load_xia_pfcu4s() - # Check that the fake ``make_device`` function was called properly - assert device_maker.call_count == 2 - device_maker.assert_called_with( - PFCUFilterBank, - labels={"filter_banks"}, - name="filter_bank1", - prefix="255idc:pfcu1:", - shutters=[[3, 4]], - ) - # Make a device with these arguments - call_args = device_maker.call_args - device = call_args.args[0](**call_args.kwargs) - # Check that the filters have the right PVs - filters = [device.filters.filter1, device.filters.filter2] - assert isinstance(filters[0], PFCUFilter) - # Check that the shutter object was created - shutter = device.shutters.shutter_0 - assert isinstance(shutter, PFCUShutter) - assert shutter.top_filter.material.pvname == "255idc:pfcu1:filter3_mat" - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov