diff --git a/doc/version_history.rst b/doc/version_history.rst
index e3b5df1e..8a8b63df 100644
--- a/doc/version_history.rst
+++ b/doc/version_history.rst
@@ -9,6 +9,7 @@ Version History
v1.25.0
-------
+* Add new ``auxtel/calibrations/power_off_atcalsys.py`` script, unit test and executable to turn off the ATCalSys white light.
* Add new ``auxtel/calibrations/power_on_atcalsys.py`` script, unit test and executable to turn on and set up the ATCalSys (ATWhiteLight and ATMonochromator) to take flats.
v1.24.2
diff --git a/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py b/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py
index d088112a..c502c5d4 100644
--- a/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py
+++ b/python/lsst/ts/standardscripts/auxtel/calibrations/__init__.py
@@ -19,4 +19,5 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from .power_off_atcalsys import *
from .power_on_atcalsys import *
diff --git a/python/lsst/ts/standardscripts/auxtel/calibrations/power_off_atcalsys.py b/python/lsst/ts/standardscripts/auxtel/calibrations/power_off_atcalsys.py
new file mode 100644
index 00000000..8c139c99
--- /dev/null
+++ b/python/lsst/ts/standardscripts/auxtel/calibrations/power_off_atcalsys.py
@@ -0,0 +1,151 @@
+# 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__ = ["PowerOffATCalSys"]
+
+import asyncio
+
+from lsst.ts import salobj
+from lsst.ts.idl.enums import ATWhiteLight
+
+
+class PowerOffATCalSys(salobj.BaseScript):
+ """Powers off the ATCalSys dome flat illuminator
+ turning white lamp off, closing the shutter and
+ stopping the chiller.
+
+ Parameters
+ ----------
+ index : `int`
+ Index of Script SAL component.
+
+ """
+
+ def __init__(self, index, add_remotes: bool = True):
+ super().__init__(
+ index=index,
+ descr="Power OFF AT Calibration System ",
+ )
+
+ self.white_light_source = None
+
+ # White lamp config
+ self.timeout_lamp_cool_down = 60 * 16
+ self.cmd_timeout = 30
+
+ # Shutter
+ self.timeout_close_shutter = 60 * 2
+
+ @classmethod
+ def get_schema(cls):
+ return None
+
+ async def configure(self, config):
+ # This script does not require any configuration
+
+ self.log.info("Configure started")
+
+ if self.white_light_source is None:
+ self.white_light_source = salobj.Remote(
+ domain=self.domain,
+ name="ATWhiteLight",
+ )
+
+ await self.white_light_source.start_task
+
+ self.log.info("Configure completed")
+
+ def set_metadata(self, metadata):
+ """Compute estimated duration."""
+
+ metadata.duration = self.timeout_lamp_cool_down
+
+ async def run(self):
+ """Run script."""
+ await self.assert_components_enabled()
+
+ await self.checkpoint("Turning lamp off")
+ await self.switch_lamp_off()
+
+ await self.checkpoint("Closing the shutter")
+ await self.white_light_source.cmd_closeShutter.start(
+ timeout=self.timeout_close_shutter
+ )
+
+ await self.checkpoint("Waiting for lamp to cool down")
+ await self.wait_for_lamp_to_cool_down()
+
+ await self.checkpoint("Stopping chiller")
+ await self.white_light_source.cmd_stopChiller.start(timeout=self.cmd_timeout)
+
+ async def switch_lamp_off(self):
+ """Switches white light source lamp off"""
+ self.white_light_source.evt_lampState.flush()
+
+ await self.white_light_source.cmd_turnLampOff.start(
+ timeout=self.timeout_lamp_cool_down
+ )
+
+ async def wait_for_lamp_to_cool_down(self):
+ """Confirm the white lamp has switched OFF and has cooled down
+ to proceed with stopping the chiller.
+
+ Raises
+ ------
+ TimeOutError:
+ If the lamp doesn't cool down and fails to turn off
+ in self.timeout_lamp_cool_down.
+ """
+ lamp_state = await self.white_light_source.evt_lampState.aget(
+ timeout=self.timeout_lamp_cool_down
+ )
+ self.log.info(
+ f"Lamp state: {ATWhiteLight.LampBasicState(lamp_state.basicState)!r}."
+ )
+ while lamp_state.basicState != ATWhiteLight.LampBasicState.OFF:
+ try:
+ lamp_state = await self.white_light_source.evt_lampState.next(
+ flush=False, timeout=self.timeout_lamp_cool_down
+ )
+ cool_down_wait_time = (
+ lamp_state.cooldownEndTime - lamp_state.private_sndStamp
+ )
+ self.log.info(
+ f"Lamp state: {ATWhiteLight.LampBasicState(lamp_state.basicState)!r}. "
+ f"Waiting {cool_down_wait_time/60:0.1f} min"
+ )
+ except asyncio.TimeoutError:
+ raise RuntimeError(
+ f"White Light Lamp failed to turn off after {self.timeout_lamp_warm_up} s."
+ )
+
+ async def assert_components_enabled(self):
+ """Check if ATWhiteLight is ENABLED
+
+ Raises
+ ------
+ RunTimeError:
+ If ATWhiteLight is not ENABLED"""
+ summary_state = await self.white_light_source.evt_summaryState.aget(
+ timeout=self.cmd_timeout
+ )
+ if summary_state.summaryState != salobj.State.ENABLED:
+ raise RuntimeError("ATWhiteLight is not ENABLED")
diff --git a/python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_off_atcalsys.py b/python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_off_atcalsys.py
new file mode 100755
index 00000000..61e040bb
--- /dev/null
+++ b/python/lsst/ts/standardscripts/data/scripts/auxtel/calibrations/power_off_atcalsys.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.auxtel.calibrations import PowerOffATCalSys
+
+asyncio.run(PowerOffATCalSys.amain())
diff --git a/tests/test_auxtel_power_off_atcalsys.py b/tests/test_auxtel_power_off_atcalsys.py
new file mode 100644
index 00000000..67950c46
--- /dev/null
+++ b/tests/test_auxtel_power_off_atcalsys.py
@@ -0,0 +1,138 @@
+# 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
+import logging
+import random
+import types
+import unittest
+
+from lsst.ts import salobj, standardscripts, utils
+from lsst.ts.idl.enums import ATWhiteLight
+from lsst.ts.standardscripts.auxtel.calibrations import PowerOffATCalSys
+
+random.seed(47) # for set_random_lsst_dds_partition_prefix
+
+logging.basicConfig()
+
+
+class TestPowerOffATCalSys(
+ standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase
+):
+ async def basic_make_script(self, index):
+ self.script = PowerOffATCalSys(index=index, add_remotes=False)
+
+ self.lamp_state = types.SimpleNamespace(
+ basicState=ATWhiteLight.LampBasicState.ON
+ )
+ self.shutter_status = types.SimpleNamespace(
+ shutterState=ATWhiteLight.ShutterState.OPEN
+ )
+
+ await self.configure_mocks()
+
+ return [
+ self.script,
+ ]
+
+ async def configure_mocks(self):
+ self.script.white_light_source = unittest.mock.AsyncMock()
+ self.script.white_light_source.start_task = utils.make_done_future()
+
+ # Configure mocks
+
+ self.script.white_light_source.configure_mock(
+ **{
+ "evt_summaryState.aget.side_effect": self.mock_get_whitelightsource_summary_state,
+ "cmd_turnLampOff.start.side_effect": self.mock_get_lamp_status,
+ "cmd_closeShutter.start.side_effect": self.mock_close_shutter,
+ }
+ )
+
+ # Mock check methods
+ self.script.wait_for_lamp_to_cool_down = unittest.mock.AsyncMock(
+ side_effect=self.mock_lamp_temp
+ )
+
+ # Summary State
+
+ async def mock_get_whitelightsource_summary_state(self, **kwargs):
+ return types.SimpleNamespace(summaryState=salobj.State.ENABLED)
+
+ # Lamp
+
+ async def mock_get_lamp_status(self, **kwargs):
+ await asyncio.sleep(0.5)
+ return self.lamp_state
+
+ async def mock_lamp_temp(self, **kwargs):
+ self.lamp_state.basicState = ATWhiteLight.LampBasicState.TURNING_OFF
+ await asyncio.sleep(15.0)
+ self.lamp_state.basicState = ATWhiteLight.LampBasicState.OFF
+
+ # Shutter
+ async def mock_close_shutter(self, **kwargs):
+ types.SimpleNamespace(shutterState=ATWhiteLight.ShutterState.OPEN)
+ await asyncio.sleep(3)
+ self.shutter_status = types.SimpleNamespace(
+ shutterState=ATWhiteLight.ShutterState.CLOSED
+ )
+
+ async def test_run_without_without_failures(self):
+ async with self.make_script():
+ await self.configure_script()
+
+ await self.run_script()
+
+ # Summary State
+ self.script.white_light_source.evt_summaryState.aget.assert_awaited_once_with(
+ timeout=self.script.cmd_timeout
+ )
+
+ # White lamp
+ self.script.white_light_source.cmd_turnLampOff.start.assert_awaited_with(
+ timeout=self.script.timeout_lamp_cool_down,
+ )
+
+ self.script.wait_for_lamp_to_cool_down.assert_awaited_once()
+
+ # Shutter
+ self.script.white_light_source.cmd_closeShutter.start.assert_awaited_with(
+ timeout=self.script.timeout_close_shutter,
+ )
+
+ # Chiller
+ self.script.white_light_source.cmd_stopChiller.start.assert_awaited_once_with(
+ timeout=self.script.cmd_timeout
+ )
+
+ # Check status
+ assert self.lamp_state.basicState == ATWhiteLight.LampBasicState.OFF
+ assert self.shutter_status.shutterState == ATWhiteLight.ShutterState.CLOSED
+
+ async def test_executable(self):
+ scripts_dir = standardscripts.get_scripts_dir()
+ script_path = scripts_dir / "auxtel" / "calibrations" / "power_off_atcalsys.py"
+ await self.check_executable(script_path)
+
+
+if __name__ == "__main__":
+ unittest.main()