diff --git a/doc/news/DM-46980.feature.rst b/doc/news/DM-46980.feature.rst new file mode 100644 index 000000000..91cb954c6 --- /dev/null +++ b/doc/news/DM-46980.feature.rst @@ -0,0 +1 @@ +Add home dome SAL Script for ``maintel``. diff --git a/python/lsst/ts/standardscripts/data/scripts/maintel/mtdome/home_dome.py b/python/lsst/ts/standardscripts/data/scripts/maintel/mtdome/home_dome.py new file mode 100755 index 000000000..66e362fd5 --- /dev/null +++ b/python/lsst/ts/standardscripts/data/scripts/maintel/mtdome/home_dome.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# This file is part of ts_standardscripts +# +# Developed for the LSST Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio + +from lsst.ts.standardscripts.maintel.mtdome import HomeDome + +asyncio.run(HomeDome.amain()) diff --git a/python/lsst/ts/standardscripts/maintel/mtdome/__init__.py b/python/lsst/ts/standardscripts/maintel/mtdome/__init__.py index bfc5d7265..0b67cd067 100644 --- a/python/lsst/ts/standardscripts/maintel/mtdome/__init__.py +++ b/python/lsst/ts/standardscripts/maintel/mtdome/__init__.py @@ -22,6 +22,7 @@ from .crawl_az import * from .disable_dome_following import * from .enable_dome_following import * +from .home_dome import * from .park_dome import * from .slew_dome import * from .unpark_dome import * diff --git a/python/lsst/ts/standardscripts/maintel/mtdome/home_dome.py b/python/lsst/ts/standardscripts/maintel/mtdome/home_dome.py new file mode 100644 index 000000000..af661a597 --- /dev/null +++ b/python/lsst/ts/standardscripts/maintel/mtdome/home_dome.py @@ -0,0 +1,111 @@ +# This file is part of ts_standardscripts +# +# Developed for the LSST Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see .`` + +__all__ = ["HomeDome"] + +import yaml +from lsst.ts import salobj +from lsst.ts.observatory.control.maintel.mtcs import MTCS, MTCSUsages + + +class HomeDome(salobj.BaseScript): + """Home azimuth of the MTDome. + + Parameters + ---------- + index : `int` + Index of Script SAL component. + + Notes + ----- + **Checkpoints** + + Homing dome: Before commanding azimuth dome to be homed. + """ + + def __init__(self, index, add_remotes: bool = True): + super().__init__(index=index, descr="Home MT dome.") + + self.mtcs = None + self.physical_az = None + self.home_dome_duration = 60.0 + + @classmethod + def get_schema(cls): + schema_yaml = """ + $schema: http://json-schema.org/draft-07/schema# + $id: https://github.com/lsst-ts/ts_standardscripts/maintel/mtdome/home_dome.yaml + title: HomeDome v1 + description: Configuration for HomeDome. + type: object + properties: + physical_az: + description: Physical azimuth position for the dome as read by markings. + type: number + ignore: + description: >- + CSCs from the group to ignore in status check. Name must + match those in self.group.components, e.g.; hexapod_1. + type: array + items: + type: string + additionalProperties: false + required: [physical_az] + """ + return yaml.safe_load(schema_yaml) + + async def configure(self, config): + """Configure script. + + Parameters + ---------- + config : `types.SimpleNamespace` + Script configuration, as defined by `schema`. + """ + self.config = config + self.physical_az = config.physical_az + + if self.mtcs is None: + self.mtcs = MTCS( + domain=self.domain, + intended_usage=MTCSUsages.Slew | MTCSUsages.StateTransition, + log=self.log, + ) + await self.mtcs.start_task + + if hasattr(self.config, "ignore"): + for comp in self.config.ignore: + if comp not in self.mtcs.components_attr: + self.log.warning( + f"Component {comp} not in CSC Group. " + f"Must be one of {self.mtcs.components_attr}. Ignoring." + ) + else: + self.log.debug(f"Ignoring component {comp}.") + setattr(self.mtcs.check, comp, False) + + def set_metadata(self, metadata): + metadata.duration = self.home_dome_duration + + async def run(self): + await self.mtcs.assert_all_enabled() + await self.checkpoint("Homing dome") + await self.mtcs.home_dome(physical_az=self.physical_az) diff --git a/tests/test_maintel_mtdome_home_dome.py b/tests/test_maintel_mtdome_home_dome.py new file mode 100644 index 000000000..bfbc23a60 --- /dev/null +++ b/tests/test_maintel_mtdome_home_dome.py @@ -0,0 +1,97 @@ +# This file is part of ts_standardscripts +# +# Developed for the LSST Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import contextlib +import unittest + +import pytest +from lsst.ts import salobj, standardscripts +from lsst.ts.idl.enums.Script import ScriptState +from lsst.ts.standardscripts.maintel.mtdome import HomeDome + + +class TestHomeDome( + standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase +): + async def basic_make_script(self, index): + self.script = HomeDome(index=index) + + return (self.script,) + + @contextlib.asynccontextmanager + async def make_dry_script(self): + async with self.make_script(self): + self.script.mtcs = unittest.mock.AsyncMock() + self.script.mtcs.assert_all_enabled = unittest.mock.AsyncMock() + self.script.mtcs.home_dome = unittest.mock.AsyncMock() + yield + + async def test_run(self): + async with self.make_dry_script(): + await self.configure_script(physical_az=300.0) + + await self.run_script() + self.script.mtcs.assert_all_enabled.assert_awaited_once() + self.script.mtcs.home_dome.assert_awaited_once() + self.script.mtcs.home_dome.assert_called_with(physical_az=300.0) + + async def test_executable(self): + scripts_dir = standardscripts.get_scripts_dir() + script_path = scripts_dir / "maintel" / "mtdome" / "home_dome.py" + await self.check_executable(script_path) + + async def test_config(self): + async with self.make_script(): + await self.configure_script(physical_az=300.0) + assert self.script.physical_az == 300.0 + + async def test_invalid_configurations(self): + # Set of invalid configurations to test, all should fail to configure + configs_bad = [ + dict(physical_az="not_valid"), + dict(physical_az=[1, 3]), + dict(physical_az=None), + ] + + async with self.make_script(): + for config in configs_bad: + with pytest.raises(salobj.ExpectedError): + await self.configure_script(**config) + assert self.script.state == ScriptState.CONFIGURE_FAILED + + async def test_configure_ignore(self): + async with self.make_script(): + components = ["mtptg"] + await self.configure_script(physical_az=300.0, ignore=components) + + assert self.script.mtcs.check.mtptg is False + + async def test_configure_ignore_not_csc_component(self): + async with self.make_script(): + components = ["not_csc_comp", "mtptg"] + await self.configure_script(physical_az=300.0, ignore=components) + + assert hasattr(self.script.mtcs, "not_csc_comp") is False + assert self.script.mtcs.check.mtptg is False + + +if __name__ == "__main__": + unittest.main()