From 816415216e988f6e57adbe32831b819a903c74fb Mon Sep 17 00:00:00 2001 From: isotuela Date: Fri, 19 May 2023 19:32:58 +0000 Subject: [PATCH] First draft --- .../auxtel/calibrations/__init__.py | 21 ++ .../auxtel/calibrations/power_on_atcalsys.py | 255 ++++++++++++++++++ .../auxtel/calibrations/power_on_atcalsys.py | 26 ++ tests/test_auxtel_power_on_atcalsys.py | 56 ++++ 4 files changed, 358 insertions(+) create mode 100644 python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py create mode 100644 python/lsst/ts/standardscripts/auxtel/calibrations/power_on_atcalsys.py create mode 100755 python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_on_atcalsys.py create mode 100644 tests/test_auxtel_power_on_atcalsys.py diff --git a/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py b/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py new file mode 100644 index 000000000..fdac5a154 --- /dev/null +++ b/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py @@ -0,0 +1,21 @@ +# 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 + +from .power_on_atcalsys import * \ No newline at end of file diff --git a/python/lsst/ts/standardscripts/auxtel/calibrations/power_on_atcalsys.py b/python/lsst/ts/standardscripts/auxtel/calibrations/power_on_atcalsys.py new file mode 100644 index 000000000..9634bce8a --- /dev/null +++ b/python/lsst/ts/standardscripts/auxtel/calibrations/power_on_atcalsys.py @@ -0,0 +1,255 @@ +# 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 + +__all__ = ["PowerOnATCalSys"] + +import asyncio +import collections + +import numpy as np +import yaml + +from lsst.ts.idl.enums.Script import ScriptState +from lsst.ts.idl.enums import ATMonochromator, ATWhiteLight + +from lsst.ts import salobj + + +class PowerOnATCalSys(salobj.BaseScript): + """Powers on the ATCalSys dome flat illuminator (ATWhiteLight and ATMonochromator) + required to perform imaging white light calibrations. + + Parameters + ---------- + index : `int` + Index of Script SAL component. + + Notes + ----- + + """ + def __init__(self, index): + super().__init__( + index=index, + descr="Power On AT Calibration System ", + ) + self.whitelightsource = salobj.Remote( + domain=self.domain, name="ATWhiteLight", log=self.log + ) + + self.monochromator = salobj.Remote( + domain=self.domain, name="ATMonochromator", log=self.log + ) + + # self.cmd_timeout = 10 + self.change_grating_time = 60 + self.open_shutter_time = 20 + self.chiller_temp_tolerance_relative = 0.1 + self.chiller_temp_time = 60 * 2 + self.track_lamp_warmup = 60 + self.lamp_warmup_time = 15 * 60 + + @classmethod + def get_schema(cls): + schema_yaml = """ + $schema: http://json-schema.org/draft-07/schema# + $id: https://github.com/lsst-ts/ts_standardscripts/auxtel/SlewTelescopeIcrs.yaml + title: PowerOnATCalSys v1 + description: Configuration for PowerOnATCalSys. + Each attribute can be specified as a scalar or array. + All arrays must have the same length (one item per image). + type: object + + properties: + chiller_temperature: + description: + default: 20 + - type: number + minimum: 0 + + whitelight_power: + description: White light power. + default: 910 + - type: number + minimum: 0 + + wavelength: + description: Wavelength (nm). 0 nm is for white light. + default: 0 + - type: number + minimum: 0 + + grating_type: + description: Grating type for each image. The choices are 0=blue, 1=red, 2=mirror. + default: 2 + anyOf: + - type: integer + enum: [0, 1, 2] + + entrance_slit_width: + description: Width of the monochrometer entrance slit (mm) + default: 5 + - type: number + minimum: 0 + + exit_slit_width: + description: Width of the monochromator entrance slit (mm) + default: 5 + - type: number + minimum: 0 + + additionalProperties: false + """ + return yaml.safe_load(schema_yaml) + + async def configure(self, config): + """Configure the script. + + Parameters + ---------- + config : ``self.cmd_configure.DataType`` + + Raises + ------ + salobj.ExpectedError : + If the data does not match the schema, or the lengths + of all values that are arrays do not match. + """ + self.log.info("Configure started") + + # self.chiller_temperature = config.chiller_temperature + + # self.whitelight_power = config.whitelight_power + + # self.wavelength = config.wavelength + + # self.grating_type = config.grating_type + + # self.entrance_slit_width = config.entrance_slit_width + + # self.exit_slit_width = config.exit_slit_width + + self.config = config + + self.log.info("Configure completed") + + def set_metadata(self, metadata): + """Compute estimated duration. + + Parameters + ---------- + metadata : SAPY_Script.Script_logevent_metadataC + """ + metadata.duration = 60 * 15 + + async def run(self): + """Run script.""" + + await self.checkpoint("Start the chiller") + + await whitelightsource.cmd_setChillerTemperature.set_start(temperature=self.chiller_temperature) + await whitelightsource.cmd_startChiller.set_start() + + # confirm the chiller is running at chiller_temperature within tolerance + try: + await asyncio.wait_for( + is_chiller_temp_within_tolerance(), self.chiller_temp_time + ) + except asyncio.TimeoutError: + self.log.info(f"Gave up waiting after {self.chiller_temp_time} for the chiller to chill to {self.chiller_temperature}") + + await self.checkpoint("Open the shutter") + + await whitelightsource.cmd_openShutter.set_start() + + # Confirm shutter is open or sent an error message? Should we include an error message if it fails rather than the RunTimeError + ## shutter_state = await whitelightsource.evt_shutterState.aget() + + await self.checkpoint("Turning on lamp") + await whitelightsource.cmd_turnLampOn.set_start(power = self.whitelight_power) + + # Confirm lamp state turns on (It will go into a warm up period before it will turn on) + try: + await asyncio.wait_for( + has_lamp_finish_warm_up(), self.lamp_warm_up_time + ), + except asyncio.TimeoutError: + self.log.info(f"Lamp didn't turn on after {self.lamp_warm_up_time} s") + + await self.checkpoint("Configuring ATMonochromator") + + await self.checkpoint("Set the grating") + + await monochromator.cmd_selectGrating.set_start(gratingType=self.grating, timeout = 180) + + await self.checkpoint("Set wavelength") + + await monochromator.cmd_changeWavelength.set_start(wavelength=self.wavelength) + + await self.checkpoint("Set slits wide open") + + await monochromator.cmd_changeSlitWidth.set_start(slit=1, slitWidth= self.entrance_slit_width) + await monochromator.cmd_changeSlitWidth.set_start(slit=2, slitWidth= self.exit_slit_width) + + params = await get_monochromator_parameters() + + script.log.info(f"ATMonochromator grating is {params[0]}, wavelength is {params[1]} nm with entry slit width {param[2]} and exit slit width {param[3]}") + + async def get_monochromator_parameters(): + tmp1 = await monochromator.evt_selectedGrating.aget() + tmp2 = await monochromator.evt_wavelength.aget() + tmp3 = await monochromator.evt_entrySlitWidth.aget() + tmp4 = await monochromator.evt_exitSlitWidth.aget() + return (tmp1.gratingType, tmp2.wavelength, tmp3.width, tmp4.width ) + + + async def has_lamp_finish_warm_up(): + lamp_done_warm_up = False + start_time_lamp_warm_up = time.time() + while not lamp_done_warm_up: + lamp_state = await whitelightsource.evt_lampState.aget() + if lamp_state.basicState == ATWhiteLight.LampBasicState.WARMUP: + warmup_time_left = lamp_state.warmupEndTime - lamp_state.private_rcvStamp + script.log.info("Time Left for lamp warmup: {} min.".format(warmup_time_left/60.)) + await asyncio.sleep(self.track_lamp_warmup) + lamp_done_warm_up = False + elif lamp_state.basicState == ATWhiteLight.LampBasicState.ON: + lamp_done_warm_up = True + warm_up_elapsed_time = time.time() - start_time_lamp_warm_up + script.log.info(f"White Light Lamp is on after {warm_up_elapsed_time} s") + else: + continue + + async def is_chiller_temp_within_tolerance(): + chiller_temp_within_tolerance = False + start_time_chill_time = time.time() + while not chiller_temp_within_tolerance: + chiller_temps = await whitelightsource.tel_chillerTemperatures.aget() + if ( + chiller_temps.setTemperature * (1.0 - self.chiller_temp_tolerance_relative) + < chiller_temps.supplyTemperature + < chiller_temps.setTemperature * (1.0 + self.chiller_temp_tolerance_relative) + ): + chiller_temp_within_tolerance = True + chill_time = time.time() - start_time_chill_time + script.log.info(f"Chiller reached target temperature within tolerance in {chill_time} s") + else: + continue + diff --git a/python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_on_atcalsys.py b/python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_on_atcalsys.py new file mode 100755 index 000000000..edc5df229 --- /dev/null +++ b/python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_on_atcalsys.py @@ -0,0 +1,26 @@ +#!/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 + +import asyncio + +from lsst.ts.standardscripts.auxtel.calibrations import PowerOnATCalSys + +asyncio.run(PowerOnATCalSys.amain()) diff --git a/tests/test_auxtel_power_on_atcalsys.py b/tests/test_auxtel_power_on_atcalsys.py new file mode 100644 index 000000000..36eec64cb --- /dev/null +++ b/tests/test_auxtel_power_on_atcalsys.py @@ -0,0 +1,56 @@ +# 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 + +import asyncio +import logging +import random +import unittest + +import pytest +from lsst.ts.idl.enums import ATMonochromator, Script + +from lsst.ts import salobj, standardscripts +from lsst.ts.standardscripts.auxtel import PowerOnATCalSys + +random.seed(47) # for set_random_lsst_dds_partition_prefix + +logging.basicConfig() + +class TestPowerOnATCalSys(standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase): + async def basic_make_script(self, index): + self.script = PowerOnATCalSys(index=index) + #self.atcs_mock = ATCSMock() + + return (self.script, self.atcs_mock) + + async def test_run(self): + async with self.make_script(): + await self.configure_script() + + await self.run_script() + + async def test_executable(self): + scripts_dir = standardscripts.get_scripts_dir() + script_path = scripts_dir / "auxtel" / "calibrations" / "power_on_atcalsys.py" + await self.check_executable(script_path) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file