From 9c28a9b3910ac4ebfc98e5a7a522777d5bf3fabe Mon Sep 17 00:00:00 2001 From: Parker Fagrelius <parfa30@gmail.com> Date: Wed, 12 Mar 2025 12:11:12 -0300 Subject: [PATCH 1/2] tests --- .../maintel/park_calibration_projector.py | 101 ++++++++++++++ .../test_park_calibration_projector.py | 123 ++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 python/lsst/ts/externalscripts/maintel/park_calibration_projector.py create mode 100644 tests/maintel/test_park_calibration_projector.py diff --git a/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py b/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py new file mode 100644 index 00000000..d5992915 --- /dev/null +++ b/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py @@ -0,0 +1,101 @@ +# This file is part of ts_externalscripts +# +# 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 <https://www.gnu.org/licenses/>. + +__all__ = ["ParkCalibrationProjector"] + + +from lsst.ts import salobj +from lsst.ts.observatory.control.maintel.mtcalsys import MTCalsys + + +class ParkCalibrationProjector(salobj.BaseScript): + """Move the calibration projector into a safe position + + Parameters + ---------- + index : int + Index of Script SAL component. + """ + + def __init__(self, index): + super().__init__( + index=index, + descr="Park Calibration Projector", + ) + + self.mtcalsys = None + + def set_metadata(self, metadata): + metadata.duration = 30 + + async def configure(self, config): + """Configure the script. + + Parameters + ---------- + config : ``self.cmd_configure.DataType`` + + """ + self.log.info("Configure started") + if self.mtcalsys is None: + self.log.debug("Creating MTCalSys.") + self.mtcalsys = MTCalsys(domain=self.domain, log=self.log) + await self.mtcalsys.start_task + + self.linearstage_led_focus = self.mtcalsys.linearstage_led_focus + self.linearstage_led_select = self.mtcalsys.linearstage_led_select + self.linearstage_projector_select = self.mtcalsys.linearstage_projector_select + self.led_projector = self.mtcalsys.rem.ledprojector + + self.log.info("Configure completed") + + async def run(self): + """Run script.""" + await self.assert_components_enabled() + + self.log.info("Parking Calibration Projector") + await self.mtcalsys.park_projector() + + # # TO-DO: DM-49065 for mtcalsys.py + # params = await self.mtcalsys.get_projector_setup() + + async def assert_components_enabled(self): + """Checks if LEDProjector and all LinearStages are ENABLED + Raises + ------ + RunTimeError: + If either component is not ENABLED""" + + comps = [ + self.linearstage_led_focus, + self.linearstage_led_select, + self.linearstage_projector_select, + self.led_projector, + ] + + for comp in comps: + summary_state = await comp.evt_summaryState.aget() + try: + summaryState = summary_state.summaryState + except NameError: + summaryState = summary_state + if salobj.State(summaryState) != salobj.State(salobj.State.ENABLED): + raise RuntimeError(f"{comp} is not ENABLED") diff --git a/tests/maintel/test_park_calibration_projector.py b/tests/maintel/test_park_calibration_projector.py new file mode 100644 index 00000000..63c9bb9a --- /dev/null +++ b/tests/maintel/test_park_calibration_projector.py @@ -0,0 +1,123 @@ +# This file is part of ts_externalscripts +# +# 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 <https://www.gnu.org/licenses/>. + +import logging +import os +import unittest + +from lsst.ts import externalscripts, salobj, standardscripts, utils +from lsst.ts.externalscripts.maintel.park_calibration_projector import ( + ParkCalibrationProjector, +) +from lsst.ts.observatory.control.maintel.mtcalsys import MTCalsys +from lsst.ts.xml.enums import Script + +index_gen = utils.index_generator() + + +class TestSetupWhiteFlats( + standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase +): + def setUp(self): + self.log = logging.getLogger(__name__) + self.log.propagate = True + + @property + def remote_group(self) -> MTCalsys: + """The remote_group property.""" + return self.mtcalsys + + async def basic_make_script(self, index): + self.log.debug("Starting basic_make script") + self.script = ParkCalibrationProjector(index=index) + + self.log.debug("Finished initializing from basic_make_script") + return (self.script,) + + async def mock_calsys(self): + """Mock Calsys CSCs""" + + self.script.linearstage_led_focus = unittest.mock.AsyncMock() + self.script.linearstage_led_focus.evt_summaryState.aget = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.linearstage_led_focus.evt_summaryState.summaryState = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.linearstage_led_select = unittest.mock.AsyncMock() + self.script.linearstage_led_select.evt_summaryState.aget = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.linearstage_led_select.evt_summaryState.summaryState = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.linearstage_projector_select = unittest.mock.AsyncMock() + self.script.linearstage_projector_select.evt_summaryState.aget = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.linearstage_projector_select.evt_summaryState.summaryState = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.led_projector = unittest.mock.AsyncMock() + self.script.led_projector.evt_summaryState.aget = unittest.mock.AsyncMock( + return_value=salobj.State.ENABLED + ) + self.script.led_projector.evt_summaryState.summaryState = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.mtcalsys.tunablelaser = unittest.mock.AsyncMock() + self.script.mtcalsys.tunablelaser.evt_summaryState.aget = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + self.script.mtcalsys.tunablelaser.evt_summaryState.summaryState = ( + unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + ) + + async def test_configure(self): + async with self.make_script(): + await self.configure_script() + assert self.script.state.state == Script.ScriptState.CONFIGURED + + async def test_run_without_failures(self): + async with self.make_script(): + await self.configure_script() + assert self.script.state.state == Script.ScriptState.CONFIGURED + + self.log.debug("Starting Mtcalsys mocks") + await self.mock_mtcalsys() + await self.mock_calsys() + + self.log.debug("Enable all CSCs") + + # Run the script + self.log.debug("Running the script") + await self.run_script() + assert self.script.state.state == Script.ScriptState.DONE + + async def test_executable(self): + scripts_dir = externalscripts.get_scripts_dir() + script_path = os.path.join( + scripts_dir, "maintel", "park_calibration_projector.py" + ) + await self.check_executable(script_path) + + if __name__ == "__main__": + unittest.main() From aeab49249de2735011dbdaef232b7b25b8e5d834 Mon Sep 17 00:00:00 2001 From: Parker Fagrelius <parfa30@gmail.com> Date: Mon, 17 Mar 2025 12:34:25 -0300 Subject: [PATCH 2/2] adding test and other files --- doc/news/DM-49346.feature.rst | 1 + .../maintel/park_calibration_projector.py | 26 +++++++++++++++++++ .../ts/externalscripts/maintel/__init__.py | 1 + .../maintel/park_calibration_projector.py | 20 ++++++++++++-- .../test_park_calibration_projector.py | 17 ++++++------ 5 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 doc/news/DM-49346.feature.rst create mode 100755 python/lsst/ts/externalscripts/data/scripts/maintel/park_calibration_projector.py diff --git a/doc/news/DM-49346.feature.rst b/doc/news/DM-49346.feature.rst new file mode 100644 index 00000000..181b35a3 --- /dev/null +++ b/doc/news/DM-49346.feature.rst @@ -0,0 +1 @@ +Included a script that parks the calibration projector in a safe place and turns off the LEDs diff --git a/python/lsst/ts/externalscripts/data/scripts/maintel/park_calibration_projector.py b/python/lsst/ts/externalscripts/data/scripts/maintel/park_calibration_projector.py new file mode 100755 index 00000000..c75d04e7 --- /dev/null +++ b/python/lsst/ts/externalscripts/data/scripts/maintel/park_calibration_projector.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# This file is part of ts_externalscripts +# +# 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 + +import asyncio + +from lsst.ts.externalscripts.maintel import ParkCalibrationProjector + +asyncio.run(ParkCalibrationProjector.amain()) diff --git a/python/lsst/ts/externalscripts/maintel/__init__.py b/python/lsst/ts/externalscripts/maintel/__init__.py index 548abdaa..6f14999f 100644 --- a/python/lsst/ts/externalscripts/maintel/__init__.py +++ b/python/lsst/ts/externalscripts/maintel/__init__.py @@ -22,6 +22,7 @@ from .make_comcam_calibrations import * from .parameter_march_comcam import * from .parameter_march_lsstcam import * +from .park_calibration_projector import * from .take_comcam_guider_image import * from .take_ptc_flats_comcam import * from .take_rotated_comcam import * diff --git a/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py b/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py index d5992915..565dcc47 100644 --- a/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py +++ b/python/lsst/ts/externalscripts/maintel/park_calibration_projector.py @@ -21,7 +21,7 @@ __all__ = ["ParkCalibrationProjector"] - +import yaml from lsst.ts import salobj from lsst.ts.observatory.control.maintel.mtcalsys import MTCalsys @@ -46,6 +46,21 @@ def __init__(self, index): def set_metadata(self, metadata): metadata.duration = 30 + @classmethod + def get_schema(cls): + schema_yaml = """ + $schema: http://json-schema.org/draft-07/schema# + $id: https://github.com/lsst-ts/ts_externalscripts/maintel/calibrations/park_calibration_projector.yaml # noqa: E501 + title: SetupWhiteFlats v1 + description: Configuration for SetupWhiteFlats. + Each attribute can be specified as a scalar or array. + All arrays must have the same length (one item per image). + type: object + + additionalProperties: false + """ + return yaml.safe_load(schema_yaml) + async def configure(self, config): """Configure the script. @@ -95,7 +110,8 @@ async def assert_components_enabled(self): summary_state = await comp.evt_summaryState.aget() try: summaryState = summary_state.summaryState - except NameError: + except Exception as e: + self.log.debug(f"Exception: {e}") summaryState = summary_state if salobj.State(summaryState) != salobj.State(salobj.State.ENABLED): raise RuntimeError(f"{comp} is not ENABLED") diff --git a/tests/maintel/test_park_calibration_projector.py b/tests/maintel/test_park_calibration_projector.py index 63c9bb9a..5bc42d0f 100644 --- a/tests/maintel/test_park_calibration_projector.py +++ b/tests/maintel/test_park_calibration_projector.py @@ -49,6 +49,8 @@ async def basic_make_script(self, index): self.log.debug("Starting basic_make script") self.script = ParkCalibrationProjector(index=index) + await self.mock_calsys() + self.log.debug("Finished initializing from basic_make_script") return (self.script,) @@ -83,13 +85,13 @@ async def mock_calsys(self): self.script.led_projector.evt_summaryState.summaryState = ( unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) ) - self.script.mtcalsys.tunablelaser = unittest.mock.AsyncMock() - self.script.mtcalsys.tunablelaser.evt_summaryState.aget = ( - unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) - ) - self.script.mtcalsys.tunablelaser.evt_summaryState.summaryState = ( - unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) - ) + # self.script.mtcalsys.tunablelaser = unittest.mock.AsyncMock() + # self.script.mtcalsys.tunablelaser.evt_summaryState.aget = ( + # unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + # ) + # self.script.mtcalsys.tunablelaser.evt_summaryState.summaryState = ( + # unittest.mock.AsyncMock(return_value=salobj.State.ENABLED) + # ) async def test_configure(self): async with self.make_script(): @@ -102,7 +104,6 @@ async def test_run_without_failures(self): assert self.script.state.state == Script.ScriptState.CONFIGURED self.log.debug("Starting Mtcalsys mocks") - await self.mock_mtcalsys() await self.mock_calsys() self.log.debug("Enable all CSCs")