Source code for dodal.devices.hutch_shutter

from bluesky.protocols import Movable
from ophyd_async.core import (
    DEFAULT_TIMEOUT,
    AsyncStatus,
    StandardReadable,
    StrictEnum,
    wait_for_value,
)
from ophyd_async.epics.core import epics_signal_r, epics_signal_w

HUTCH_SAFE_FOR_OPERATIONS = 0  # Hutch is locked and can't be entered


class ShutterNotSafeToOperateError(Exception):
    pass


[docs] class ShutterDemand(StrictEnum): OPEN = "Open" CLOSE = "Close" RESET = "Reset"
[docs] class ShutterState(StrictEnum): FAULT = "Fault" OPEN = "Open" OPENING = "Opening" CLOSED = "Closed" CLOSING = "Closing"
[docs] class HutchInterlock(StandardReadable): """Device to check the interlock status of the hutch.""" def __init__(self, bl_prefix: str, name: str = "") -> None: self.status = epics_signal_r(float, bl_prefix + "-PS-IOC-01:M14:LOP") super().__init__(name) # TODO replace with read # See https://github.com/DiamondLightSource/dodal/issues/651
[docs] async def shutter_safe_to_operate(self) -> bool: """If the status value is 0, hutch has been searched and locked and it is safe \ to operate the shutter. If the status value is not 0 (usually set to 7), the hutch is open and the \ shutter should not be in use. """ interlock_state = await self.status.get_value() return interlock_state == HUTCH_SAFE_FOR_OPERATIONS
[docs] class HutchShutter(StandardReadable, Movable): """Device to operate the hutch shutter. When a demand is sent, the device should first check the hutch status \ and raise an error if it's not interlocked (searched and locked), meaning it's not \ safe to operate the shutter. If the requested shutter position is "Open", the shutter control PV should first \ go to "Reset" and then move to "Open". This is because before opening the hutch \ shutter, the interlock status PV (`-PS-SHTR-01:ILKSTA`) will show as `failed` until \ the hutch shutter is reset. This will set the interlock status to `OK`, allowing \ for shutter operations. Until this step is done, the hutch shutter can't be opened. The reset is not needed for closing the shutter. """ def __init__(self, prefix: str, name: str = "") -> None: self.control = epics_signal_w(ShutterDemand, prefix + "CON") self.status = epics_signal_r(ShutterState, prefix + "STA") bl_prefix = prefix.split("-")[0] self.interlock = HutchInterlock(bl_prefix) super().__init__(name) @AsyncStatus.wrap async def set(self, value: ShutterDemand): interlock_state = await self.interlock.shutter_safe_to_operate() if not interlock_state: raise ShutterNotSafeToOperateError( "The hutch has not been locked, not operating shutter." ) if value == ShutterDemand.OPEN: await self.control.set(ShutterDemand.RESET, wait=True) await self.control.set(value, wait=True) return await wait_for_value( self.status, match=ShutterState.OPEN, timeout=DEFAULT_TIMEOUT ) else: await self.control.set(value, wait=True) return await wait_for_value( self.status, match=ShutterState.CLOSED, timeout=DEFAULT_TIMEOUT )