Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unpark MTDome logic for MTCS #161

Merged
merged 2 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/news/DM-45610.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement dome unpark in MTCS.
51 changes: 51 additions & 0 deletions python/lsst/ts/observatory/control/maintel/mtcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ def __init__(
self._dome_az_in_position: typing.Union[None, asyncio.Event] = None
self._dome_el_in_positio: typing.Union[None, asyncio.Event] = None

self.dome_az_unpark_offset = 0.1 # A small move to un-park the Dome

# timeout to raise m1m3, in seconds.
self.m1m3_raise_timeout = 600.0
# time it takes for m1m3 to settle after a slew finishes.
Expand Down Expand Up @@ -668,6 +670,55 @@ async def park_dome(self) -> None:
check_in_position=True,
)

async def unpark_dome(self) -> None:
"""Un-Park the dome by moving it a small delta amount."""

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

self.log.debug("Checking if the dome is currently PARKED.")
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 currently PARKED. Proceeding to un-park.")

current_position = await self.rem.mtdome.tel_azimuth.aget(
timeout=self.fast_timeout
)

unparked_position = (
current_position.positionActual + self.dome_az_unpark_offset
)

self.rem.mtdome.evt_azMotion.flush()

# We don't specify the dome velocity as it defaults to
# zero, which is what we want.
await self.rem.mtdome.cmd_moveAz.set_start(
position=unparked_position,
timeout=self.park_dome_timeout,
)

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

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
)
else:
self.log.info("Dome is not in PARKED state. No need to un-park.")

def set_azel_slew_checks(self, wait_dome: bool) -> typing.Any:
"""Handle azEl slew to wait or not for the dome.

Expand Down
55 changes: 36 additions & 19 deletions python/lsst/ts/observatory/control/mock/mtcs_async_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ async def setup_types(self) -> None:
)

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

Expand Down Expand Up @@ -234,11 +234,12 @@ async def setup_mtdome(self) -> None:
"""Augment MTDome."""
mtdome_mocks = {
"tel_azimuth.next.side_effect": self.mtdome_tel_azimuth_next,
"tel_azimuth.aget.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,
"cmd_moveAz.set_start.side_effect": self.mtdome_cmd_move_az,
"evt_azMotion.aget.side_effect": self.mtdome_evt_az_motion_next,
"evt_azMotion.next.side_effect": self.mtdome_evt_az_motion_next,
}

self.mtcs.rem.mtdome.configure_mock(**mtdome_mocks)
Expand Down Expand Up @@ -478,35 +479,51 @@ 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(
self._mtdome_evt_az_motion = types.SimpleNamespace(
state=MTDome.MotionState.PARKED, inPosition=True
)

async def mtdome_evt_az_motion_state_next(
self._mtdome_tel_azimuth = types.SimpleNamespace(
positionActual=360.0 - 32.0, # as per current logic in MTDome
# do_park: 360.0 - DOME_AZIMUTH_OFFSET
positionCommanded=0.0,
)

async def mtdome_cmd_move_az(self, *args: typing.Any, **kwargs: typing.Any) -> None:
asyncio.create_task(self._mtdome_move_az())

async def _mtdome_move_az(self, *args: typing.Any, **kwargs: typing.Any) -> None:
# Mock implementation of dome park , unpark and slew_dome_to
self.log.info("Dome moveAz command executed")
await asyncio.sleep(self.heartbeat_time * 2)
# This wait time and Dome MotionSate MOVING is to mock the
# test_unpark_dome
self._mtdome_evt_az_motion = types.SimpleNamespace(
state=MTDome.MotionState.MOVING, inPosition=False
)

# The following delay is timed so that the MOVING state above
# remains long enough for the test_unpark_dome to pick it
# up and complete successfully. Same wise the test_slew_dome_to
# expects at some point an ENABLED and inPosition.
await asyncio.sleep(self.heartbeat_time * 3)
self.log.info("Slew dome to azimuth command executed")
self._mtdome_evt_az_motion = types.SimpleNamespace(
state=MTDome.MotionState.ENABLED, inPosition=True
)

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

async def mtm1m3_evt_detailed_state(
self, *args: typing.Any, **kwargs: typing.Any
) -> types.SimpleNamespace:
await asyncio.sleep(self.heartbeat_time / 4.0)
return self._mtm1m3_evt_detailed_state

async def mtdome_cmd_move_az(
self, position: float, velocity: float, timeout: float
) -> None:
asyncio.create_task(self._mtdome_move_az())

async def _mtdome_move_az(self) -> None:
# Mock implementation of cmd_moveAz
await asyncio.sleep(self.heartbeat_time * 3)
self.log.info("Slew dome to azimuth command executed")
self._mtdome_evt_azMotion_state = types.SimpleNamespace(
state=MTDome.MotionState.ENABLED, inPosition=True
)

async def mtm1m3_evt_hp_test_status(
self, *args: typing.Any, **kwargs: typing.Any
) -> types.SimpleNamespace:
Expand Down
15 changes: 15 additions & 0 deletions tests/maintel/test_mtcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,21 @@ async def test_park_dome(self) -> None:
), "Dome did not reach the PARKED state."
assert az_motion.inPosition, "Dome is not in position."

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

# set initial PARKED state
await self.mtcs.park_dome()

await self.mtcs.unpark_dome()

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

assert (
az_motion.state != MTDome.MotionState.PARKED
), "Dome still in PARKED state."

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

Expand Down
Loading