Skip to content

Commit

Permalink
More checks before moving the dome and disable overwatcher on error
Browse files Browse the repository at this point in the history
  • Loading branch information
albireox committed Jan 9, 2025
1 parent 3a15aac commit e5a4f92
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 44 deletions.
53 changes: 40 additions & 13 deletions src/gort/devices/enclosure.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,26 @@ async def status(self, get_registers: bool = False):

return reply.flatten()

async def allowed_to_move(self):
"""Checks if the dome is allowed to move."""

dome: ActorReply = await self.actor.commands.dome.commands.status(timeout=5)
labels = dome.get("dome_status_labels", default=[])

if "DRIVE_ERROR" in labels:
return False

if "UNKNOWN" in labels:
return False

if "DRIVE_AVAILABLE" not in labels:
return False

if await self.is_local():
return False

return True

async def _prepare_telescopes(self, force: bool = False):
"""Moves telescopes to park position before opening/closing the enclosure."""

Expand Down Expand Up @@ -161,6 +181,23 @@ async def _prepare_telescopes(self, force: bool = False):

await asyncio.gather(*park_coros)

async def _check_dome(self):
"""Checks if we can operate the dome or raises an exception."""

if await self.allowed_to_move():
return

if await self.is_local():
raise GortEnclosureError(
"Cannot open the enclosure while in local mode.",
error_code=ErrorCode.ENCLOSURE_IN_LOCAL,
)
else:
raise GortEnclosureError(
"Dome found in error state. Cannot move the dome.",
error_code=ErrorCode.ENCLOSURE_ERROR,
)

async def open(self, park_telescopes: bool = True):
"""Open the enclosure dome.
Expand All @@ -172,12 +209,7 @@ async def open(self, park_telescopes: bool = True):
"""

is_local = await self.is_local()
if is_local:
raise GortEnclosureError(
"Cannot open the enclosure while in local mode.",
error_code=ErrorCode.LOCAL_MODE_FAILED,
)
await self._check_dome()

if park_telescopes:
await self._prepare_telescopes()
Expand Down Expand Up @@ -212,12 +244,7 @@ async def close(
"""

is_local = await self.is_local()
if is_local:
raise GortEnclosureError(
"Cannot close the enclosure while in local mode.",
error_code=ErrorCode.LOCAL_MODE_FAILED,
)
await self._check_dome()

self.write_to_log("Closing the dome ...", level="info")
await self.gort.notify_event(Event.DOME_CLOSING)
Expand Down Expand Up @@ -288,7 +315,7 @@ async def is_local(self):
if safety_status_labels is None:
raise GortEnclosureError(
"Cannot determine if enclosure is in local mode.",
error_code=ErrorCode.LOCAL_MODE_FAILED,
error_code=ErrorCode.ENCLOSURE_IN_LOCAL,
)

return "LOCAL" in safety_status_labels
Expand Down
2 changes: 1 addition & 1 deletion src/gort/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ErrorCode(Enum):
INVALID_CALIBRATION_SEQUENCE = 303
NPS_ERROR = 400
ENCLOSURE_ERROR = 500
LOCAL_MODE_FAILED = 501
ENCLOSURE_IN_LOCAL = 501
DOOR_STATUS_FAILED = 502
GUIDER_ERROR = 600
INVALID_PIXEL_NAME = 601
Expand Down
58 changes: 31 additions & 27 deletions src/gort/overwatcher/helpers/dome.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import asyncio
import enum

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Coroutine

from lvmopstools.retrier import Retrier

Expand Down Expand Up @@ -141,11 +141,8 @@ async def _move(
if open and not self.overwatcher.state.safe:
raise GortError("Cannot open the dome when conditions are unsafe.")

is_local = await self.gort.enclosure.is_local()
if is_local:
raise GortError("Cannot move the dome in local mode.")

failed: bool = False
if not await self.gort.enclosure.allowed_to_move():
raise GortError("Dome found in invalid or unsafe state.")

try:
if status & DomeStatus.MOVING:
Expand All @@ -169,24 +166,29 @@ async def _move(

# Sometimes the open/close could fail but actually the dome is open/closed.
if open and not (status & DomeStatus.OPEN):
failed = True
raise GortError("Dome is not open after a move command.")
elif not open and not (status & DomeStatus.CLOSED):
failed = True

if failed:
await self.overwatcher.notify(
"The dome has failed to open/close. Disabling the Overwatcher "
"to prevent further attempts. Please check the dome immediately, "
"it may be partially or fully open.",
level="critical",
)
raise GortError("Dome is not closed after a move command.")

# Release the lock here. force_disable() may require closing the dome.
if self._move_lock.locked():
self._move_lock.release()
async def _run_or_disable(self, coro: Coroutine):
"""Runs a coroutine or disables the overwatcher if it fails."""

await self.overwatcher.force_disable()
raise
try:
await coro
except Exception:
await self.overwatcher.notify(
"The dome has failed to open/close. Disabling the Overwatcher "
"to prevent further attempts. Please check the dome immediately, "
"it may be partially or fully open.",
level="critical",
)

# Release the lock here.
if self._move_lock.locked():
self._move_lock.release()

await self.overwatcher.force_disable()
raise

async def open(self, park: bool = True):
"""Opens the dome."""
Expand All @@ -206,7 +208,7 @@ async def open(self, park: bool = True):
await self.stop()
status = await self.status()

await self._move(status, open=True, park=park)
await self._run_or_disable(self._move(status, open=True, park=park))

async def close(self, park: bool = True, retry: bool = False):
"""Closes the dome."""
Expand All @@ -226,11 +228,13 @@ async def close(self, park: bool = True, retry: bool = False):
await self.stop()
status = await self.status()

await self._move(
status,
open=False,
park=park,
retry_without_parking=retry,
await self._run_or_disable(
self._move(
status,
open=False,
park=park,
retry_without_parking=retry,
)
)

async def stop(self):
Expand Down
9 changes: 6 additions & 3 deletions src/gort/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from dataclasses import dataclass, field
from types import SimpleNamespace

from typing import TYPE_CHECKING, Callable, Literal
from typing import TYPE_CHECKING, Any, Callable, Literal

import unclick
from aiormq import AMQPConnectionError, ChannelInvalidStateError
Expand Down Expand Up @@ -327,11 +327,14 @@ def flatten(self):

return result

def get(self, key: str):
def get(self, key: str, default: Any = ...):
"""Returns the first occurrence of a keyword in the reply list."""

for reply in self.replies:
if key in reply:
return reply[key]

return None
if default is not ...:
return default

raise KeyError(f"Keyword {key!r} not found in replies.")

0 comments on commit e5a4f92

Please sign in to comment.