diff --git a/doc/news/DM-46979.feature.rst b/doc/news/DM-46979.feature.rst new file mode 100644 index 000000000..7a486dd0e --- /dev/null +++ b/doc/news/DM-46979.feature.rst @@ -0,0 +1,2 @@ +Add SAL scripts to park and unpark the TMA for ``maintel``. + diff --git a/python/lsst/ts/standardscripts/data/scripts/maintel/mtmount/park_mount.py b/python/lsst/ts/standardscripts/data/scripts/maintel/mtmount/park_mount.py new file mode 100755 index 000000000..1c65929d5 --- /dev/null +++ b/python/lsst/ts/standardscripts/data/scripts/maintel/mtmount/park_mount.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.mtmount import ParkMount + +asyncio.run(ParkMount.amain()) diff --git a/python/lsst/ts/standardscripts/data/scripts/maintel/mtmount/unpark_mount.py b/python/lsst/ts/standardscripts/data/scripts/maintel/mtmount/unpark_mount.py new file mode 100755 index 000000000..e2604bdad --- /dev/null +++ b/python/lsst/ts/standardscripts/data/scripts/maintel/mtmount/unpark_mount.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.mtmount import UnparkMount + +asyncio.run(UnparkMount.amain()) diff --git a/python/lsst/ts/standardscripts/maintel/mtmount/__init__.py b/python/lsst/ts/standardscripts/maintel/mtmount/__init__.py new file mode 100644 index 000000000..7d4e0f359 --- /dev/null +++ b/python/lsst/ts/standardscripts/maintel/mtmount/__init__.py @@ -0,0 +1,23 @@ +# 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 . + +from .park_mount import * +from .unpark_mount import * diff --git a/python/lsst/ts/standardscripts/maintel/mtmount/park_mount.py b/python/lsst/ts/standardscripts/maintel/mtmount/park_mount.py new file mode 100644 index 000000000..03300847f --- /dev/null +++ b/python/lsst/ts/standardscripts/maintel/mtmount/park_mount.py @@ -0,0 +1,109 @@ +# 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__ = ["ParkMount"] + +import yaml +from lsst.ts import salobj +from lsst.ts.observatory.control.maintel.mtcs import MTCS, MTCSUsages +from lsst.ts.xml.enums import MTMount + + +class ParkMount(salobj.BaseScript): + """Park Mount for the MTMount. + + Parameters + ---------- + index : `int` + Index of Script SAL component. + + Notes + ----- + **Checkpoints** + + None + """ + + def __init__(self, index): + super().__init__(index=index, descr="Park Mount for the MTMount.") + + self.mtcs = None + + @classmethod + def get_schema(cls): + schema_yaml = """ + $schema: http://json-schema.org/draft-07/schema# + $id: https://github.com/lsst-ts/ts_standardscripts/maintel/mtmount/park_mount.yaml + title: ParkMount v1 + description: Configuration for ParkMount. + type: object + properties: + position: + description: The position to park the MTMount. + type: string + enum: ["ZENITH", "HORIZON"] + 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: [position] + """ + return yaml.safe_load(schema_yaml) + + def set_metadata(self, metadata): + pass + + async def configure(self, config): + """Configure script. + + Parameters + ---------- + config : `types.SimpleNamespace` + Script configuration, as defined by `schema`. + """ + self.config = config + self.position = MTMount.ParkPosition[config.position] + + 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) + + async def run(self): + await self.mtcs.park_mount(position=self.position) diff --git a/python/lsst/ts/standardscripts/maintel/mtmount/unpark_mount.py b/python/lsst/ts/standardscripts/maintel/mtmount/unpark_mount.py new file mode 100644 index 000000000..e098b1c96 --- /dev/null +++ b/python/lsst/ts/standardscripts/maintel/mtmount/unpark_mount.py @@ -0,0 +1,101 @@ +# +# 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__ = ["UnparkMount"] + +import yaml +from lsst.ts import salobj +from lsst.ts.observatory.control.maintel.mtcs import MTCS, MTCSUsages + + +class UnparkMount(salobj.BaseScript): + """Un-park Mount for the MTMount. + + Parameters + ---------- + index : `int` + Index of Script SAL component. + + Notes + ----- + **Checkpoints** + + None + """ + + def __init__(self, index): + super().__init__(index=index, descr="Unpark Mount for the MTMount.") + + self.mtcs = None + + @classmethod + def get_schema(cls): + schema_yaml = """ + $schema: http://json-schema.org/draft-07/schema# + $id: https://github.com/lsst-ts/ts_standardscripts/maintel/mtmount/unpark_mount.yaml + title: UnparkMount v1 + description: Configuration for UnparkMount. + type: object + properties: + 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 + """ + return yaml.safe_load(schema_yaml) + + def set_metadata(self, metadata): + pass + + async def configure(self, config): + """Configure script. + + Parameters + ---------- + config : `types.SimpleNamespace` + Script configuration, as defined by `schema`. + """ + self.config = config + + 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) + + async def run(self): + await self.mtcs.unpark_mount() diff --git a/tests/test_maintel_mtmount_park_mount.py b/tests/test_maintel_mtmount_park_mount.py new file mode 100644 index 000000000..632666b46 --- /dev/null +++ b/tests/test_maintel_mtmount_park_mount.py @@ -0,0 +1,78 @@ +# 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 + +from lsst.ts import standardscripts +from lsst.ts.standardscripts.maintel.mtmount import ParkMount +from lsst.ts.xml.enums import MTMount + + +class TestParkMount( + standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase +): + async def basic_make_script(self, index): + self.script = ParkMount(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.park_mount = unittest.mock.AsyncMock() + yield + + async def test_executable(self): + scripts_dir = standardscripts.get_scripts_dir() + script_path = scripts_dir / "maintel" / "mtmount" / "park_mount.py" + await self.check_executable(script_path) + + async def test_configure_ignore(self): + async with self.make_script(): + components = ["mtptg"] + await self.configure_script(position="ZENITH", ignore=components) + assert self.script.mtcs.check.mtptg is False + + async def test_configure_ignore_not_mtcs_component(self): + async with self.make_script(): + # Test the ignore feature with one non-MTCS component. + components = ["not_mtcs_comp", "mtptg"] + await self.configure_script(position="ZENITH", ignore=components) + assert not hasattr(self.script.mtcs.check, "not_mtcs_comp") + assert self.script.mtcs.check.mtptg is False + + async def test_park_zenith(self): + async with self.make_dry_script(): + await self.configure_script(position="ZENITH") + await self.run_script() + self.script.mtcs.park_mount.assert_called_with( + position=MTMount.ParkPosition.ZENITH + ) + + async def test_park_horizon(self): + async with self.make_dry_script(): + await self.configure_script(position="HORIZON") + await self.run_script() + self.script.mtcs.park_mount.assert_called_with( + position=MTMount.ParkPosition.HORIZON + ) diff --git a/tests/test_maintel_mtmount_unpark_mount.py b/tests/test_maintel_mtmount_unpark_mount.py new file mode 100644 index 000000000..d3812ce25 --- /dev/null +++ b/tests/test_maintel_mtmount_unpark_mount.py @@ -0,0 +1,67 @@ +# 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 + +from lsst.ts import standardscripts +from lsst.ts.standardscripts.maintel.mtmount import UnparkMount + + +class TestUnparkMount( + standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase +): + async def basic_make_script(self, index): + self.script = UnparkMount(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.unpark_mount = unittest.mock.AsyncMock() + yield + + async def test_executable(self): + scripts_dir = standardscripts.get_scripts_dir() + script_path = scripts_dir / "maintel" / "mtmount" / "unpark_mount.py" + await self.check_executable(script_path) + + async def test_configure_ignore(self): + async with self.make_script(): + components = ["mtptg"] + await self.configure_script(ignore=components) + assert self.script.mtcs.check.mtptg is False + + async def test_configure_ignore_not_mtcs_component(self): + async with self.make_script(): + # Test the ignore feature with one non-MTCS component. + components = ["not_mtcs_comp", "mtptg"] + await self.configure_script(ignore=components) + assert not hasattr(self.script.mtcs.check, "not_mtcs_comp") + assert self.script.mtcs.check.mtptg is False + + async def test_run(self): + async with self.make_dry_script(): + await self.configure_script() + await self.run_script() + self.script.mtcs.unpark_mount.assert_called_once()