-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split handlers and add signal handling
- Loading branch information
Showing
6 changed files
with
338 additions
and
264 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
# | ||
# @Author: José Sánchez-Gallego ([email protected]) | ||
# @Date: 2023-11-26 | ||
# @Filename: __init__.py | ||
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) | ||
|
||
from __future__ import annotations | ||
|
||
from .ln2 import LN2Handler | ||
from .thermistor import ThermistorHandler | ||
from .valve import ValveHandler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,287 +2,29 @@ | |
# -*- coding: utf-8 -*- | ||
# | ||
# @Author: José Sánchez-Gallego ([email protected]) | ||
# @Date: 2023-11-11 | ||
# @Filename: ln2fill.py | ||
# @Date: 2023-11-26 | ||
# @Filename: ln2.py | ||
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause) | ||
|
||
from __future__ import annotations | ||
|
||
import asyncio | ||
import dataclasses | ||
from time import time | ||
|
||
from typing import Coroutine, Optional | ||
from typing import Coroutine | ||
|
||
import sshkeyboard | ||
from pydantic import Field, model_validator | ||
from pydantic.dataclasses import dataclass | ||
from rich.progress import TaskID | ||
|
||
from sdsstools.configuration import RecursiveDict | ||
|
||
from ln2fill import config, log | ||
from ln2fill.handlers.valve import ValveHandler | ||
from ln2fill.tools import ( | ||
TimerProgressBar, | ||
cancel_nps_threads, | ||
cancel_task, | ||
get_spectrograph_status, | ||
read_thermistors, | ||
valve_on_off, | ||
) | ||
|
||
|
||
PROGRESS_BAR = TimerProgressBar(console=log.rich_console) | ||
|
||
|
||
@dataclass | ||
class ThermistorHandler: | ||
"""Reads a thermistor, potentially closing the valve if active. | ||
Parameters | ||
---------- | ||
valve_handler | ||
The `.ValveHandler` instance associated with this thermistor. | ||
interval | ||
The interval in seconds between thermistor reads. | ||
min_open_time | ||
The minimum valve open time. The thermistor will not be read until | ||
the minimum time has been reached. | ||
close_valve | ||
If ``True``, closes the valve once the thermistor is active. | ||
min_active_time | ||
Number of seconds the thermistor must be active before the valve is | ||
closed. | ||
""" | ||
|
||
valve_handler: ValveHandler | ||
interval: float = 1 | ||
min_open_time: float = Field(default=0.0, ge=0.0) | ||
close_valve: bool = True | ||
min_active_time: float = Field(default=10.0, ge=0.0) | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.valve_handler = self.valve_handler | ||
|
||
# Unix time at which we started to monitor. | ||
self._start_time: float = -1 | ||
|
||
# Unix time at which the thermistor became active. | ||
self._active_time: float = -1 | ||
|
||
thermistor_channel = self.valve_handler.thermistor_channel | ||
assert thermistor_channel | ||
|
||
self.thermistor_channel: str = thermistor_channel | ||
|
||
async def read_thermistor(self): | ||
"""Reads the thermistor and returns its state.""" | ||
|
||
thermistors = await read_thermistors() | ||
return thermistors[self.thermistor_channel] | ||
|
||
async def start_monitoring(self): | ||
"""Monitors the thermistor and potentially closes the valve.""" | ||
|
||
await asyncio.sleep(self.min_open_time) | ||
|
||
while True: | ||
thermistor_status = await self.read_thermistor() | ||
if thermistor_status is True: | ||
if self._active_time < 0: | ||
self._active_time = time.time() | ||
if time.time() - self._active_time > self.min_active_time: | ||
break | ||
|
||
await asyncio.sleep(self.interval) | ||
|
||
if self.close_valve: | ||
await self.valve_handler.finish_fill() | ||
|
||
|
||
@dataclass | ||
class ValveHandler: | ||
"""Handles a valve, including opening and closing, timeouts, and thermistors. | ||
Parameters | ||
---------- | ||
valve | ||
The name of the valve. Additional information such as thermistor channel, | ||
actor names, etc. are determined from the configuration file if not | ||
explicitely provided. | ||
thermistor_channel | ||
The channel of the thermistor associated with the valve. | ||
nps_actor | ||
The NPS actor that command the valve. | ||
show_progress_bar | ||
Whether to show a progress bar during fills. | ||
""" | ||
|
||
valve: str | ||
thermistor_channel: Optional[str] = dataclasses.field(default=None, repr=False) | ||
nps_actor: Optional[str] = dataclasses.field(default=None, repr=False) | ||
show_progress_bar: Optional[bool] = dataclasses.field(default=True, repr=False) | ||
|
||
@model_validator(mode="after") | ||
def validate_fields(self): | ||
if self.valve not in config["valves"]: | ||
raise ValueError(f"Unknown valve {self.valve!r}.") | ||
|
||
if self.thermistor_channel is None: | ||
thermistor_key = f"valves.{self.valve}.thermistor" | ||
self.thermistor_channel = config.get(thermistor_key, self.valve) | ||
|
||
if self.nps_actor is None: | ||
self.nps_actor = config.get(f"valves.{self.valve}.actor") | ||
if self.nps_actor is None: | ||
raise ValueError(f"Cannot find NPS actor for valve {self.valve!r}.") | ||
|
||
return self | ||
|
||
def __post_init__(self): | ||
self.thermistor = ThermistorHandler(self) | ||
|
||
self._thread_id: int | None = None | ||
self._progress_bar_id: TaskID | None = None | ||
|
||
self._monitor_task: asyncio.Task | None = None | ||
self._timeout_task: asyncio.Task | None = None | ||
|
||
self.event = asyncio.Event() | ||
self.active: bool = False | ||
|
||
async def start_fill( | ||
self, | ||
min_open_time: float = 0.0, | ||
timeout: float | None = None, | ||
use_thermistor: bool = True, | ||
): | ||
"""Starts a fill. | ||
Parameters | ||
---------- | ||
min_open_time | ||
Minimum time to keep the valve open. | ||
timeout | ||
The maximumtime to keep the valve open. If ``None``, defaults to | ||
the ``max_open_time`` value. | ||
use_thermistor | ||
Whether to use the thermistor to close the valve. If ``True`` and | ||
``fill_time`` is not ``None``, ``fill_time`` become the maximum | ||
open time. | ||
""" | ||
|
||
if timeout is None: | ||
# Some hardcoded hard limits. | ||
if self.valve == "purge": | ||
timeout = 2000 | ||
else: | ||
timeout = 600 | ||
|
||
await self._set_state(True, timeout=timeout, use_script=True) | ||
|
||
if use_thermistor: | ||
self.thermistor.min_open_time = min_open_time | ||
self._monitor_task = asyncio.create_task(self.thermistor.start_monitoring()) | ||
|
||
if self.show_progress_bar: | ||
if self.valve.lower() == "purge": | ||
initial_description = "Purge in progress ..." | ||
complete_description = "Purge complete" | ||
else: | ||
initial_description = "Fill in progress ..." | ||
complete_description = "Fill complete" | ||
|
||
self._progress_bar_id = await PROGRESS_BAR.add_timer( | ||
timeout, | ||
label=self.valve, | ||
initial_description=initial_description, | ||
complete_description=complete_description, | ||
) | ||
|
||
await asyncio.sleep(2) | ||
self._timeout_task = asyncio.create_task(self._schedule_timeout_task(timeout)) | ||
|
||
self.active = True | ||
|
||
self.event.clear() | ||
await self.event.wait() | ||
|
||
async def _schedule_timeout_task(self, timeout: float): | ||
"""Schedules a task to cancel the fill after a timeout.""" | ||
|
||
await asyncio.sleep(timeout) | ||
await self.finish_fill() | ||
|
||
async def finish_fill(self): | ||
"""Finishes the fill, closing the valve.""" | ||
|
||
await self._set_state(False) | ||
|
||
await cancel_task(self._monitor_task) | ||
self._monitor_task = None | ||
|
||
if self._progress_bar_id is not None: | ||
await PROGRESS_BAR.stop_timer(self._progress_bar_id) | ||
self._progress_bar_id = None | ||
|
||
await cancel_task(self._timeout_task) | ||
self._timeout_task = None | ||
|
||
if not self.event.is_set(): | ||
self.event.set() | ||
|
||
self.active = False | ||
|
||
async def _set_state( | ||
self, | ||
on: bool, | ||
use_script: bool = True, | ||
timeout: float | None = None, | ||
): | ||
"""Sets the state of the valve. | ||
Parameters | ||
---------- | ||
on | ||
Whether to open or close the valve. | ||
use_script | ||
Whether to use the ``cycle_with_timeout`` script to set a timeout | ||
after which the valve will be closed. | ||
timeout | ||
Maximum open valve time. | ||
""" | ||
|
||
assert self.nps_actor is not None | ||
|
||
# If there's already a thread running for this valve, we cancel it. | ||
if self._thread_id is not None: | ||
await cancel_nps_threads(self.nps_actor, self._thread_id) | ||
|
||
thread_id = await valve_on_off( | ||
self.valve, | ||
on, | ||
timeout=timeout, | ||
use_script=use_script, | ||
) | ||
|
||
if thread_id is not None: | ||
self._thread_id = thread_id | ||
|
||
if on: | ||
if thread_id is not None: | ||
log.debug( | ||
f"Valve {self.valve!r} was opened with timeout={timeout} " | ||
f"(thread_id={thread_id})." | ||
) | ||
else: | ||
log.debug(f"Valve {self.valve!r} was closed.") | ||
|
||
|
||
@dataclass | ||
class LN2Handler: | ||
"""The main LN2 purge/fill handlerclass. | ||
|
Oops, something went wrong.