Source code for dodal.devices.beamlines.i15_1.blower

import asyncio
from dataclasses import dataclass
from functools import cached_property

import numpy as np
from daq_config_server.client import ConfigClient
from daq_config_server.models.i15_1.xpdf_parameters import (
    TemperatureControllerParams,
)
from ophyd_async.core import (
    MovableLogic,
    Reference,
    SignalR,
    SignalRW,
    StandardMovable,
    StandardReadable,
    TimeoutCalculator,
    set_and_wait_for_other_value,
    soft_signal_rw,
)
from ophyd_async.core import StandardReadableFormat as Format
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw

from dodal.common.enums import ValveState
from dodal.devices.beamlines.i15_1.safe_or_beam_positioner import SafeOrBeamPositioner


[docs] @dataclass class TemperatureMoveLogic(MovableLogic[float]): pneumatic: SignalR[ValveState] settle_time_s: SignalRW[float] # We could work this out better based on ramp rate but would need more calibration # to make sure it's accurate, for now an hour is fine TIMEOUT = 60 * 60 async def stop(self): await self.setpoint.set(0) async def move(self, new_position: float, timeout: TimeoutCalculator) -> None: pneumatic_state = await self.pneumatic.get_value() if new_position > 0 and pneumatic_state != ValveState.OPEN: raise ValueError("Blower cannot be turned on pneumatic is not open") # If we're turning the blower off we do not need to wait as the temperature # reading is just the latent heat in the device and the sample is not being heated if new_position == 0: await self.setpoint.set(0) else: await set_and_wait_for_other_value( self.setpoint, new_position, self.readback, lambda v: bool(np.isclose(v, new_position)), timeout=self.TIMEOUT, ) await asyncio.sleep(await self.settle_time_s.get_value())
[docs] class Temperature(StandardReadable, StandardMovable[float]): def __init__( self, prefix: str, pneumatic_pv: str, settle_time_s: Reference[SignalRW[float]], ) -> None: with self.add_children_as_readables(format=Format.HINTED_SIGNAL): self._temperature_rbv = epics_signal_r(float, f"{prefix}PV:RBV") with self.add_children_as_readables(format=Format.CONFIG_SIGNAL): self._temperature_sp = epics_signal_rw(float, f"{prefix}SP") self._pneumatic = epics_signal_r(ValveState, pneumatic_pv) self._settle_time_s = settle_time_s super().__init__() @cached_property def movable_logic(self) -> MovableLogic: return TemperatureMoveLogic( setpoint=self._temperature_sp, readback=self._temperature_rbv, pneumatic=self._pneumatic, settle_time_s=self._settle_time_s(), )
[docs] class Blower(SafeOrBeamPositioner): """Blows hot air on to the sample, can be moved out of the beam to allow room for a robot load. Temperatures are set in C. When a temperature is set the device will wait to reach that temperature and then wait a settle time as defined in `settle_time_s`. Setting a temperature of 0 will turn the blower off. For safety reasons the heater should only be turned on (set to a >0 temperature) if the given pneumatic is open to allow airflow. """ DEFAULT_SETTLE_TIME = 180 def __init__( self, prefix: str, motion_pv: str, pneumatic_pv: str, config_client: ConfigClient, xpdf_parameters_path: str, name: str = "", ): self.ramp_rate_c_per_sec = epics_signal_rw( float, f"{prefix}RR", f"{prefix}RR:RBV" ) self.settle_time_s = soft_signal_rw(float, self.DEFAULT_SETTLE_TIME) with self.add_children_as_readables(): self.temperature = Temperature( prefix, pneumatic_pv, Reference(self.settle_time_s) ) super().__init__(motion_pv, config_client, xpdf_parameters_path, name) def get_config(self) -> TemperatureControllerParams: return self.get_full_config().blower