Skip to content

Commit

Permalink
fleshing out startup methods
Browse files Browse the repository at this point in the history
  • Loading branch information
weatherhead99 committed Nov 22, 2023
1 parent b03f154 commit 18fe3c0
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 40 deletions.
73 changes: 47 additions & 26 deletions python/lsst/ts/observatory/control/auxtel/atcalsys.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
from typing import List, Optional, NamedTuple
from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput
from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput, CalibrationSequenceStepBase
from lsst.ts import salobj
from lsst.ts.idl.enums import ATMonochromator
from lsst.ts.idl.enums import ATWhiteLight
import asyncio
import astropy.units as un
from astropy.units import Quantity
from datetime import datetime

from collections.abc import Awaitable
from dataclasses import dataclass

class ATSpectrographSlits(NamedTuple):
FRONTENTRANCE: float
FRONTEXIT: float


@dataclass
class ATCalibrationSequenceStep(CalibrationSequenceStepBase):
grating: ATMonochromator.Grating
latiss_filter: str
latiss_grating: str
entrance_slit_width: float
exit_slit_width: float
fs_exp_time: float
fs_n_exp: int
em_exp_time: float
em_n_exp: int

class ATCalsys(BaseCalsys, HardcodeCalsysThroughput):
"""class which specifically handles the calibration system for auxtel"""

Expand Down Expand Up @@ -52,27 +65,24 @@ async def setup_for_wavelength(
self.log.debug(f"calculated slit widthsare {slit_widths}")
self.log.debug(f"calculated grating is {grating}")

monoch_fut = self._sal_cmd_helper(
"monochromator",
monoch_fut = self._sal_cmd(
self.ATMonoChromator,
"updateMonochromatorSetup",
gratingType=grating,
frontExitSlitWidth=slit_widths.FRONTEXIT,
frontEntranceSlitWdth=slit_widths.FRONTENTRACE,
wavelength=wavelen,
)

elect_fut = self._sal_cmd_helper("electrometer", "performZeroCalib")
elect_fut2 = self._sal_cmd_helper(
"electrometer",
elect_fut = self._sal_cmd("electrometer", "performZeroCalib")
elect_fut2 = self._sal_cmd(
self.Electrometer,
"setDigitalFilter",
activateFilter=False,
activateAvgFilter=False,
activateMedFilter=False,
)

# TODO: electrometer
# TODO: fibre spectrograph

asyncio.wait(
[monoch_fut, elect_fut, elect_fut2], return_when=asyncio.ALL_COMPLETED
)
Expand Down Expand Up @@ -110,7 +120,6 @@ async def verify_chiller_operation(self):
f"Chiller ambient temperature: {chiller_temps.ambientTemperature:0.1f} C")



async def turn_on_light(self, lamp_power: Quantity["power"]) -> None:
#check lamp state first
lamp_state = await self._sal_readvalue_helper(self.ATWhiteLight, "lampState")
Expand All @@ -127,21 +136,43 @@ async def turn_on_light(self, lamp_power: Quantity["power"]) -> None:
#turn on lamp and let it warm up
await self._sal_cmd(self.ATWhiteLight, "turnLampOn", power=power_watts)



async def wait_ready(self) -> None:
#in the case of auxtel, need to wait for lamp to have warmed up
#check that lamp state
lamp_state = await self._sal_waitevent(self.ATWhiteLight, "lampState")
lamp_state = await self._sal_waitevent(self.ATWhiteLight, "lampState", run_immediate=False)
if lamp_state == ATWhiteLight.LampBasicState.On:
return

if lamp_state not in {ATWhiteLight.LampBasicState.Warmup, ATWhiteLight.LampBasicState.TurningOn}:
raise RuntimeError("unexpected lamp state when waiting for readiness!")


async def _chiller_setup(self, turnon: bool=True):
async def _electrometer_expose(self, exp_time: float) -> Awaitable[str]:
await self._sal_cmd(self.Electrometer, "startScanDt", scanDuration=exp_time,
run_immediate=False)
lfa_obj = self._sal_waitevent(self.Electrometer, "largeFileObjectAvailable",
run_immediate=False)
return lfa_obj.url

async def _spectrograph_expose(self, exp_time: float, numExposures: int) -> Awaitable[str]:
await self._sal_cmd(self.ATSpectrograph, "expose", numExposures = numExposures)
lfa_obj = await self._sal_waitevent(self.ATSpectrograph, "largeFileObjectAvailable",
run_immediate=False)
return lfa_obj.url


def _chiller_temp_check(temps) -> bool:
self.log.debug(f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C "
f"[set:{temps.setTemperature} deg].")
pct_dev: float = (temps.supplyTemperature - temps.setTemperature) / temps.setTemperature

if pct_dev <= self.CHILLER_TEMP_REL_TOL:
self.log.info(
f"Chiller reached target temperature, {temps.supplyTemperature:0.1f} deg ")
return True
return False

async def _chiller_setup(self, turnon: bool=True):
chiller_setpoint_temp: float = self.CHILLER_SETPOINT_TEMP.to(un.s).value
chiller_wait_timeout: float = self.CHILLER_COOLDOWN_TIMEOUT.to(un.s).value
await self._sal_cmd(self.ATWhiteLight, "setChillerTemperature",
Expand All @@ -152,19 +183,9 @@ async def _chiller_setup(self, turnon: bool=True):
chiller_temp_gen = self._sal_telem_gen(self.ATWhiteLight, "chillerTemperatures")

now = datetime.now()
def temp_checker(temps) -> bool:
self.log.debug(f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C "
f"[set:{temps.setTemperature} deg].")
pct_dev: float = (temps.supplyTemperature - temps.setTemperature) / temps.setTemperature

if pct_dev <= self.CHILLER_TEMP_REL_TOL:
self.log.info(
f"Chiller reached target temperature, {temps.supplyTemperature:0.1f} deg ")
return True
return False

try:
await self._long_wait(chiller_temp_gen, chiller_wait_timeout, temp_checker)
await self._long_wait(chiller_temp_gen, chiller_wait_timeout, self._chiller_temp_check)
except TimeoutError as err:
nowfail = datetime.now()
wait_time: float = (nowfail - now).total_seconds()
Expand Down
42 changes: 28 additions & 14 deletions python/lsst/ts/observatory/control/base_calsys.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from .remote_group import RemoteGroup
from typing import Iterable, Optional, Tuple, List, Union
from typing import Sequence, Mapping, TypeAlias
Expand All @@ -16,11 +17,19 @@
from astropy.units import ampere, watt, nm, Quantity
import astropy.units as un


Responsivity: TypeAlias = Quantity[ampere / watt]

@dataclass
class CalibrationSequenceStepBase:
wavelength: float
n_exp: int
exp_time: float


TaskOrCoro = Union[asyncio.Task, Coroutine]


class CalsysThroughputCalculationMixin:
"""mixin class to allow pluggable source for calculation of throughputs"""

Expand Down Expand Up @@ -174,29 +183,26 @@ def __init__(
self._cmd_timeout = cmd_timeout

def _sal_cmd(
self, salobj: salobj.Remote, cmdname: str, run_immediate: bool = True, **setargs
self, salobj: salobj.Remote, cmdname: str, run_immediate: bool = True, **setargs
) -> TaskOrCoro:
cmdfun: salobj.topics.RemoteCommand = getattr(salobj, f"cmd_{cmdname}")
cmdfun.set(**setargs)
pkgtask = lambda: cmdfun.start(timeout=self._cmd_timeout)
pkgtask = cmdfun.set_start(**setargs, timeout=self._cmd_timeout)
if run_immediate:
return asyncio.createtask(pkgtask())
return pkgtask()
return asyncio.createtask(pkgtask)
return pkgtask

def _sal_waitevent(self, salobj: salobj.Remote, evtname: str, run_immediate: bool=True, flush: bool=True,
**evtargs) -> TaskOrCoro:
cmdfun: salobj.topics.RemoteEvent = getattr(salobj, f"evt_{evtname}")
pkgtask = lambda: cmdfun.next(timeout=self._cmd_timeout, flush=flush)
pkgtask = cmdfun.next(timeout=self._cmd_timeout, flush=flush)
if run_immediate:
return asyncio.create_task(pkgtask())
return pkgtask()
return asyncio.create_task(pkgtask)
return pkgtask

def _lfa_event(self, salobj: salobj.Remote, telname: str, run_immediate: bool = True, flush: bool=True, **evtargs) -> TaskOrCoro:
return _sal_waitevent_helper(salobj, "largeFileObjectAvailable", run_immediate, flush, **evtargs)
return self._sal_waitevent(salobj, "largeFileObjectAvailable", run_immediate, flush, **evtargs)

def _sal_telem_gen(self, salobj, telname) -> AsyncGenerator:
if isinstance(salobj, str):
salobj = getattr(self, salobj)
def _sal_telem_gen(self, salobj: salobj.Remote, telname: str) -> AsyncGenerator:
cmdfun: salobj.topics.RemoteTelemetry = getattr(salobj, f"tel_{telname}")

async def gen():
Expand Down Expand Up @@ -256,6 +262,11 @@ def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float:
def pd_exposure_time_for_nelectrons(self, nelec: float) -> float:
pass


@abstractmethod
async def powerup_sequence_run(self, scriptobj, **kwargs) -> AsyncGenerator:
pass

@abstractmethod
async def turn_on_light(self, **kwargs) -> None:
"""awaitable command which turns on the calibration light, having
Expand Down Expand Up @@ -295,12 +306,15 @@ def wavelen(self) -> Quantity[nm]:
"""returns the currently configured wavelength"""

@abstractmethod
async def take_data(self):
async def take_detector_data(self):
"""This will fire off all async tasks to take calibration data in sequence, and return locations a
nd metadata about the files supplied etc"""


@abstractmethod
async def gen_calibration_auxiliaries(self):
pass

async def wait_ready(self):
""" Method to wait for prepared state for taking data - e.g. lamps on, warmed up, laser warmed up etc"""


0 comments on commit 18fe3c0

Please sign in to comment.