Source code for id3c.devices.omega_laser_interlock

"""
Set up the bidirectional sample_stage.omega <-> laser_optics interlock.

This module is specific to the one interlocked pair listed below.
Additional interlocks belong in their own ``<pair>_interlock.py``
modules with their own ``setup_<pair>_interlock`` entry points.

Run once at startup, after ``make_devices()`` has populated the ophyd
registry, e.g.::

    from .devices.omega_laser_interlock import setup_omega_laser_interlock
    setup_omega_laser_interlock(oregistry)

Idempotent: safe to call again after re-loading devices.

Interlock relationships installed
---------------------------------

* ``sample_stage.omega`` is blocked unless ``laser_optics.is_out`` is
  True.  Mid-motion: subscribed to ``laser_optics.us.user_readback``
  and ``laser_optics.ds.user_readback``; an excursion that takes the
  optics out of the OUT window will stop omega and fail the move with
  :class:`~id3c.devices.interlocked_motor.MotionInterlock`.

* ``laser_optics.us`` and ``laser_optics.ds`` are blocked whenever
  ``sample_stage.omega`` is moving.  Mid-motion: subscribed to
  ``omega.motor_is_moving`` (the .MOVN field), so a laser-axis move
  in progress will be aborted if an omega motion starts.  This is
  the conservative, position-free choice; angular danger-zone gating
  is appropriate for an EPICS-IOC interlock, not this Python layer.

Either device missing from the registry is logged and skipped; the
other side's wiring is still attempted.
"""

from __future__ import annotations

import logging

[docs] logger = logging.getLogger(__name__)
[docs] def setup_omega_laser_interlock(oregistry) -> None: """Install laser_optics <-> sample_stage.omega interlocks.""" laser = oregistry.get("laser_optics", None) if hasattr(oregistry, "get") else None if laser is None: try: laser = oregistry["laser_optics"] except Exception: laser = None stage = oregistry.get("sample_stage", None) if hasattr(oregistry, "get") else None if stage is None: try: stage = oregistry["sample_stage"] except Exception: stage = None if laser is None or stage is None: logger.warning( "setup_omega_laser_interlock: missing device(s); " "laser_optics=%r sample_stage=%r. Interlock NOT installed.", laser, stage, ) return omega = getattr(stage, "omega", None) if omega is None: logger.warning( "setup_omega_laser_interlock: sample_stage has no 'omega' attribute. " "Interlock NOT installed." ) return # omega blocked unless laser is OUT. omega.interlock = lambda: laser.is_out omega.interlock_description = "laser_optics OUT" omega.interlock_watch = ( laser.us.user_readback, laser.ds.user_readback, ) # laser axes blocked while omega is moving. See module docstring # for the rationale (conservative, position-free). def _laser_permit(): return not bool(omega.motor_is_moving.get()) for axis in (laser.us, laser.ds): axis.interlock = _laser_permit axis.interlock_description = "sample_stage.omega stationary" axis.interlock_watch = (omega.motor_is_moving,) logger.info( "setup_omega_laser_interlock: installed omega<->laser_optics interlocks " "(omega blocked unless laser OUT; laser axes blocked while omega moves)." )