Skip to content

Commit

Permalink
Implement dome parking / DM-45609
Browse files Browse the repository at this point in the history
- Add the `park_dome` method in `MTCS`:
  - Ensures the dome is enabled before initiating the park sequence.
  - Flush events before issuing the remote dome park command.
  - Commands the dome to park and waits for the dome's azimuth
    motion state to reach `PARKED`.
  - Skips parking if the dome is already in the `PARKED` state.
  - Added the `wait_for_dome_state` method to periodically check the
    dome's `azMotion` event until it reaches the `PARKED` state with
    `inPosition=True`, or until the `park_dome_timeout` (initially set
    to 10 minutes/600 seconds) is reached.

- Unit test:
  - Added a new unit test `test_park_dome`
  - Updated the `MTCSAsyncMock` class to simulate dome parking
    behavior by mocking the `evt_azMotion` event to simulate the dome
    reaching the `PARKED` state with `inPosition=True`.
  • Loading branch information
iglesu committed Sep 19, 2024
1 parent cab4bdb commit 687b104
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 3 deletions.
95 changes: 92 additions & 3 deletions python/lsst/ts/observatory/control/maintel/mtcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
import numpy as np
from astropy.coordinates import Angle
from lsst.ts import salobj, utils
from lsst.ts.idl.enums import MTM1M3, MTM2, MTPtg, MTRotator
from lsst.ts.utils import angle_diff
from lsst.ts.xml.enums import MTM1M3, MTM2, MTDome, MTPtg, MTRotator

try:
from lsst.ts.xml.tables.m1m3 import FATable
Expand Down Expand Up @@ -141,6 +141,10 @@ def __init__(
self.dome_flat_el = self.dome_park_el
self.dome_slew_tolerance = Angle(1.5 * u.deg)

# TODO (DM-45609): This is an initial guess for the time it takes the
# dome to park. It might need updating.
self.park_dome_timeout = 600

self._dome_az_in_position: typing.Union[None, asyncio.Event] = None
self._dome_el_in_positio: typing.Union[None, asyncio.Event] = None

Expand Down Expand Up @@ -508,9 +512,9 @@ async def wait_for_dome_inposition(
Parameters
----------
timeout: `float`
How to to wait for mount to be in position (in seconds).
How to wait for mount to be in position (in seconds).
wait_settle: `bool`
After receiving the in position command add an addional settle
After receiving the in position command, add an additional settle
wait? (default: True)
Returns
Expand Down Expand Up @@ -579,6 +583,91 @@ async def dome_el_in_position(self) -> str:
await self._dome_el_in_position.wait()
return "Dome elevation in position."

async def wait_for_dome_state(
self,
expected_states: set[MTDome.MotionState],
bad_states: set[MTDome.MotionState],
timeout: float,
check_in_position: bool = False,
) -> None:
"""Wait for a specific dome state.
Parameters
----------
expected_states : set[MTDome.MotionState]
Valid states to transition into while un-parking.
bad_states : set[MTDome.MotionState]
States that are not allowed while un-parking and should raise an
error.
timeout : float
Maximum time to wait for the correct state.
Raises
------
RuntimeError
If a bad state is encountered or the expected state is not reached
in time.
"""

def dome_ready(az_motion: salobj.type_hints.BaseMsgType) -> bool:
return (
az_motion.state in expected_states and az_motion.inPosition
if check_in_position
else az_motion.state in expected_states
)

az_motion = await self.rem.mtdome.evt_azMotion.aget(timeout=timeout)

while not dome_ready(az_motion):
az_motion = await self.rem.mtdome.evt_azMotion.next(
Flush=False, timeout=timeout
)

if az_motion.state in bad_states:
raise RuntimeError(
f"Dome transitioned to an invalid state: {MTDome.MotionState(az_motion.state).name}"
)

self.log.debug(
f"Dome state: {MTDome.MotionState(az_motion.state).name}, inPosition: {az_motion.inPosition}"
)

async def park_dome(self) -> None:
"""Park the dome by moving it to the park azimuth."""
self.log.info("Parking dome")

await self.assert_all_enabled(
message="All components need to be enabled for parking the Dome."
)

# check first if Dome is already in PARKED state
az_motion = await self.rem.mtdome.evt_azMotion.aget(timeout=self.fast_timeout)

if az_motion.state == MTDome.MotionState.PARKED:
self.log.info("Dome is already in PARKED state.")
else:
self.rem.mtdome.evt_azMotion.flush()

await self.rem.mtdome.cmd_park.start(timeout=self.long_timeout)

# Define expected and bad states for parking
expected_states = {MTDome.MotionState.PARKED}
bad_states = {
MTDome.MotionState.ERROR,
MTDome.MotionState.UNDETERMINED,
MTDome.MotionState.DISABLED,
MTDome.MotionState.DISABLING,
}

self.log.info("Waiting for dome to reach the PARKED state.")

await self.wait_for_dome_state(
expected_states,
bad_states,
timeout=self.park_dome_timeout,
check_in_position=True,
)

def set_azel_slew_checks(self, wait_dome: bool) -> typing.Any:
"""Handle azEl slew to wait or not for the dome.
Expand Down
26 changes: 26 additions & 0 deletions python/lsst/ts/observatory/control/mock/mtcs_async_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from lsst.ts.idl.enums import MTM1M3
from lsst.ts.observatory.control.maintel.mtcs import MTCS, MTCSUsages
from lsst.ts.observatory.control.mock import RemoteGroupAsyncMock
from lsst.ts.xml.enums import MTDome


class MTCSAsyncMock(RemoteGroupAsyncMock):
Expand Down Expand Up @@ -119,6 +120,11 @@ async def setup_types(self) -> None:
positionCommanded=0.0,
)

# MTDome Motion PARKED state. State is set by the mocked park cmd.
self._mtdome_evt_azMotion_state = types.SimpleNamespace(
state=MTDome.MotionState.UNDETERMINED, inPosition=False
)

# MTM1M3 data
self._mtm1m3_evt_detailed_state = types.SimpleNamespace(
detailedState=idl.enums.MTM1M3.DetailedState.PARKED
Expand Down Expand Up @@ -229,6 +235,9 @@ async def setup_mtdome(self) -> None:
mtdome_mocks = {
"tel_azimuth.next.side_effect": self.mtdome_tel_azimuth_next,
"tel_lightWindScreen.next.side_effect": self.mtdome_tel_light_wind_screen_next,
"cmd_park.start.side_effect": self.mtdome_cmd_park,
"evt_azMotion.aget.side_effect": self.mtdome_evt_az_motion_state_next,
"evt_azMotion.next.side_effect": self.mtdome_evt_az_motion_state_next,
}

self.mtcs.rem.mtdome.configure_mock(**mtdome_mocks)
Expand Down Expand Up @@ -461,6 +470,23 @@ async def mtdome_tel_light_wind_screen_next(
) -> types.SimpleNamespace:
return self._mtdome_tel_light_wind_screen

async def mtdome_cmd_park(self, timeout: float) -> None:
asyncio.create_task(self._mtdome_park())

async def _mtdome_park(self) -> None:
# Mock implementation of cmd_park
await asyncio.sleep(self.heartbeat_time)
self.log.info("Dome park command executed")
self._mtdome_evt_azMotion_state = types.SimpleNamespace(
state=MTDome.MotionState.PARKED, inPosition=True
)

async def mtdome_evt_az_motion_state_next(
self, *args: typing.Any, **kwargs: typing.Any
) -> types.SimpleNamespace:
await asyncio.sleep(self.heartbeat_time * 3)
return self._mtdome_evt_azMotion_state

async def mtm1m3_evt_detailed_state(
self, *args: typing.Any, **kwargs: typing.Any
) -> types.SimpleNamespace:
Expand Down
18 changes: 18 additions & 0 deletions tests/maintel/test_mtcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from lsst.ts.idl.enums import MTM1M3, MTM2
from lsst.ts.observatory.control.mock.mtcs_async_mock import MTCSAsyncMock
from lsst.ts.observatory.control.utils import RotType
from lsst.ts.xml.enums import MTDome


class TestMTCS(MTCSAsyncMock):
Expand Down Expand Up @@ -764,6 +765,23 @@ async def test_offset_xy_absorb(self) -> None:
num=0,
)

async def test_park_dome(self) -> None:
await self.mtcs.enable()
await self.mtcs.assert_all_enabled()

# Call the park_dome method
await self.mtcs.park_dome()

az_motion = await self.mtcs.rem.mtdome.evt_azMotion.aget(
timeout=self.mtcs.park_dome_timeout
)

# Check the state of the azMotion event
assert (
az_motion.state == MTDome.MotionState.PARKED
), "Dome did not reach the PARKED state."
assert az_motion.inPosition, "Dome is not in position."

async def test_slew_dome_to(self) -> None:
az = 90.0

Expand Down

0 comments on commit 687b104

Please sign in to comment.