diff --git a/CHANGELOG.md b/CHANGELOG.md index fe9a8f6..0f4ec28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Next version +### 🚀 New + +* Add option `--require-all-thermistors`. When passed, the thermistors don't close the valve when they become active. Once all thermistors are active, the valves are all closed at the same time. This can potentially prevent overpressures in the last one or two cryostat being filled as the other valves close. + ### ✨ Improved * Improve handling of keyboard interrupt during post-processing. diff --git a/src/lvmcryo/__main__.py b/src/lvmcryo/__main__.py index caefc51..d07b2a2 100644 --- a/src/lvmcryo/__main__.py +++ b/src/lvmcryo/__main__.py @@ -157,10 +157,22 @@ async def ln2( bool, Option( " /--no-use-thermistors", + envvar="LVMCRYO_USE_THERMISTORS", help="Use thermistor values to determine purge/fill time.", rich_help_panel="Purge and fill options", ), ] = True, + require_all_thermistors: Annotated[ + bool, + Option( + "--require-all-thermistors", + envvar="LVMCRYO_REQUIRE_ALL_THERMISTORS", + help="If set, waits until all thermistors have activated before closing " + "any of the valves. This prevents overpressures in the cryostats when only" + "some of the valves are open. Ignore if --no-use-thermistors is used. ", + rich_help_panel="Purge and fill options", + ), + ] = False, check_pressures: Annotated[ bool, Option( @@ -421,6 +433,7 @@ async def ln2( email=email, email_level=email_level, use_thermistors=use_thermistors, + require_all_thermistors=require_all_thermistors, check_pressures=check_pressures, check_temperatures=check_temperatures, max_pressure=max_pressure, diff --git a/src/lvmcryo/config.py b/src/lvmcryo/config.py index 9b95d27..fc7e6f6 100644 --- a/src/lvmcryo/config.py +++ b/src/lvmcryo/config.py @@ -106,6 +106,7 @@ class Config(BaseModel): dry_run: bool = False use_thermistors: bool = True + require_all_thermistors: bool=False check_pressures: bool = True check_temperatures: bool = True max_pressure: float | None = None diff --git a/src/lvmcryo/handlers/ln2.py b/src/lvmcryo/handlers/ln2.py index 97af5bc..d36cbc1 100644 --- a/src/lvmcryo/handlers/ln2.py +++ b/src/lvmcryo/handlers/ln2.py @@ -25,6 +25,7 @@ from lvmopstools.devices.specs import spectrograph_pressures, spectrograph_temperatures from lvmopstools.devices.thermistors import read_thermistors +from sdsstools.utils import GatheringTaskGroup from lvmcryo.config import ValveConfig, get_internal_config from lvmcryo.handlers.thermistor import ThermistorMonitor @@ -347,6 +348,7 @@ async def fill( self, cameras: list[str] | None = None, use_thermistors: bool = True, + require_all_thermistors: bool = False, min_fill_time: float | None = None, max_fill_time: float | None = None, prompt: bool | None = None, @@ -360,6 +362,9 @@ async def fill( used to instantiate the `.LN2Handler` instance. use_thermistors Whether to use the thermistors to close the valves. + require_all_thermistors + Whether to require all thermistors to be active before closing the + valves. If `False`, valves as closed as their thermistors become active. min_fill_time The minimum time to keep the fill valves open. Only relevant if using the thermistors. @@ -391,6 +396,7 @@ async def fill( min_open_time=min_fill_time or 0.0, max_open_time=max_fill_time, use_thermistor=use_thermistors, + close_on_active=not require_all_thermistors, ) ) @@ -417,6 +423,14 @@ async def fill( raise finally: + if use_thermistors and require_all_thermistors: + # If we are waiting for all thermistors to be active, + # we need to close all valves now. + self.log.info("Closing all valves.") + async with GatheringTaskGroup() as group: + for valve_handler in self.valve_handlers.values(): + group.create_task(valve_handler._set_state(False)) + self.event_times.fill_complete = get_now() if prompt: sshkeyboard.stop_listening() diff --git a/src/lvmcryo/handlers/thermistor.py b/src/lvmcryo/handlers/thermistor.py index 7e2ee22..346f56f 100644 --- a/src/lvmcryo/handlers/thermistor.py +++ b/src/lvmcryo/handlers/thermistor.py @@ -130,6 +130,8 @@ def __post_init__(self): self.log = self.valve_handler.log self.thermistor_monitor = ThermistorMonitor(interval=self.monitoring_interval) + + self.active: bool = False self.first_active: datetime | None = None async def start_monitoring(self): @@ -262,10 +264,17 @@ async def start_monitoring(self): f"Thermistor {self.channel!r} has been active for more than " f"{elapsed_active:.1f} seconds." ) + self.active = True if self.close_valve: self.valve_handler.log.debug( f"Closing valve {self.valve_handler.valve!r} " "due to thermistor feedback." ) - await self.valve_handler.finish() + else: + self.valve_handler.log.warning( + f"Thermistor {self.channel!r} is active. Calling " + "ValveHandler.finish() but not closing the valve." + ) + + await self.valve_handler.finish(close_valve=self.close_valve) diff --git a/src/lvmcryo/handlers/valve.py b/src/lvmcryo/handlers/valve.py index 7f25e5d..6688e5f 100644 --- a/src/lvmcryo/handlers/valve.py +++ b/src/lvmcryo/handlers/valve.py @@ -224,6 +224,7 @@ async def start_fill( min_open_time: float = 0.0, max_open_time: float | None = None, use_thermistor: bool = True, + close_on_active: bool = True, ): """Starts a fill. @@ -238,6 +239,9 @@ async def start_fill( Whether to use the thermistor to close the valve. If ``True`` and ``fill_time`` is not ``None``, ``fill_time`` become the maximum open time. + close_on_active + If ``use_thermistor=True``, closes the valve when the thermistor becomes + active. Otherwise just blocks while the thermistor is inactive. """ @@ -260,6 +264,7 @@ async def start_fill( self.thermistor = ThermistorHandler( self, channel=thermistor_channel, + close_valve=close_on_active, **self.thermistor_info, ) self.thermistor.min_open_time = min_open_time @@ -294,10 +299,11 @@ async def _schedule_timeout(self, timeout: float): await asyncio.sleep(timeout) await self.finish(did_timeout=True) - async def finish(self, did_timeout: bool = False): + async def finish(self, close_valve: bool = True, did_timeout: bool = False): """Finishes the fill, closing the valve.""" - await self._set_state(False, did_timeout=did_timeout) + if close_valve: + await self._set_state(False, did_timeout=did_timeout) self._monitor_task = await cancel_task(self._monitor_task) self._timeout_task = await cancel_task(self._timeout_task) diff --git a/src/lvmcryo/runner.py b/src/lvmcryo/runner.py index 0b5afe6..e94e6e8 100644 --- a/src/lvmcryo/runner.py +++ b/src/lvmcryo/runner.py @@ -134,6 +134,7 @@ async def ln2_runner( max_fill_time = config.fill_time or config.max_fill_time await handler.fill( use_thermistors=config.use_thermistors, + require_all_thermistors=config.require_all_thermistors, min_fill_time=config.min_fill_time, max_fill_time=max_fill_time, prompt=not config.no_prompt,