예제 #1
0
    async def mcp_semaphore_ok(self, command: HALCommandType):
        """Returns the semaphore if the semaphore is owned by the TCC or nobody."""

        mcp_model = self.actor.models["mcp"]

        sem = mcp_model["semaphoreOwner"]
        if sem is None:
            sem_show_cmd = await self._send_command(
                command,
                "mcp",
                "sem.show",
                time_limit=20.0,
                raise_on_fail=False,
            )

            if sem_show_cmd.status.did_fail:
                raise HALError("Cannot get mcp semaphore. Is the MCP alive?")

            sem = mcp_model["semaphoreOwner"]

        if ((sem[0] != "TCC:0:0") and (sem[0] != "") and (sem[0] != "None")
                and (sem[0] is not None)):
            raise HALError(
                f"Cannot axis init: Semaphore is owned by  {sem[0]}. "
                "If you are the owner (e.g., via MCP Menu), release it and try again. "
                "If you are not the owner, confirm that you can steal "
                "it from them, then issue: mcp sem.steal")

        return sem
예제 #2
0
파일: __init__.py 프로젝트: sdss/HAL
    async def _send_command(
        self,
        command: HALCommandType,
        target: str,
        cmd_str: str,
        raise_on_fail: bool = True,
        **kwargs,
    ):
        """Sends a command to a target."""

        bypasses = self.actor.helpers.bypasses

        # If the helper is bypassed, just returns a fake done command.
        if (self.name and self.name in bypasses) or ("all" in bypasses):
            command.warning(f"Bypassing command '{target} {cmd_str}'")
            cmd = Command()
            cmd.set_status(CommandStatus.DONE)
            return cmd

        if self.actor.tron is None or self.actor.tron.connected() is False:
            raise HALError("Not connected to Tron. Cannot send commands.")

        cmd = await command.send_command(target, cmd_str, **kwargs)
        if raise_on_fail and cmd.status.did_fail:
            if cmd.status == CommandStatus.TIMEDOUT:
                raise HALError(f"Command '{target} {cmd_str}' timed out.")
            else:
                raise HALError(f"Command '{target} {cmd_str}' failed.")

        return cast(Command, cmd)
예제 #3
0
    async def axis_init(self, command: HALCommandType) -> bool:
        """Executes TCC axis init or fails."""

        status = await self._send_command(
            command,
            "tcc",
            "axis status",
            time_limit=20.0,
            raise_on_fail=False,
        )
        if status.status.did_fail:
            raise HALError("'tcc status' failed. Is the TCC connected?")

        if self.check_stop_in() is True:
            raise HALError("Cannot tcc axis init because of bad axis status: "
                           "Check stop buttons on Interlocks panel.")

        sem = await self.mcp_semaphore_ok(command)
        if sem is False:
            raise HALError("Failed getting the semaphore information.")

        if sem == "TCC:0:0" and self.axes_are_clear():
            command.debug(text="Axes clear and TCC has semaphore. "
                          "No axis init needed, so none sent.")
            return True

        command.debug(text="Sending tcc axis init.")
        axis_init_cmd_str = "axis init"
        if self.below_alt_limit():
            command.warning(
                text=
                "Altitude below interlock limit! Only initializing altitude "
                "and rotator: cannot move in az.")
            axis_init_cmd_str += " rot,alt"
        axis_init_cmd = await self._send_command(
            command,
            "tcc",
            axis_init_cmd_str,
            time_limit=20.0,
            raise_on_fail=False,
        )

        if axis_init_cmd.status.did_fail:
            command.error("Cannot slew telescope: failed tcc axis init.")
            raise HALError(
                "Cannot slew telescope: check and clear interlocks?")

        return True
예제 #4
0
파일: apogee.py 프로젝트: sdss/HAL
    async def set_dither_position(
        self,
        command: HALCommandType,
        position: str,
        force: bool = False,
    ):
        """Sets the dither mechanism to the commanded position."""

        position = position.upper()
        if position not in ["A", "B"]:
            raise HALError(f"Invalid dither position {position}.")

        current_position = self.get_dither_position()
        if current_position is None:
            command.warning("Current dither position is unknown.")

        if current_position == position and force is False:
            return None

        dither_command = await self._send_command(
            command,
            "apogee",
            f"dither namedpos={position}",
            time_limit=config["timeouts"]["apogee_dither"],
        )

        return dither_command
예제 #5
0
    async def goto_position(
        self,
        command: HALCommandType,
        where: str | dict[str, float],
    ):
        """Executes the goto command.

        Parameters
        ----------
        command
            The actor command.
        where
            The name of the goto entry in the configuration file, or a
            dictionary with the keys ``(alt, az, rot)`` in degrees.

        """

        config = command.actor.config

        if isinstance(where, str):
            if where not in config["goto"]:
                raise HALError(f"Cannot find goto position '{where}'.")

            alt = config["goto"][where]["alt"]
            az = config["goto"][where]["az"]
            rot = config["goto"][where]["rot"]

            where = {"alt": alt, "az": az, "rot": rot}

        if self.is_slewing:
            raise HALError("TCC is already slewing.")

        # await self.axis_init(command)

        # Even if this is already checked in axis_init(), let's check again that the
        # axes are ok, but if alt < limit, we only check az and alt because we won't
        # move in altitude.
        result = self.axes_are_clear()
        if not result:
            raise HALError("Some axes are not clear. Cannot continue.")

        # Now do the actual slewing.
        slew_result = await self.do_slew(command, where)
        if slew_result is False:
            raise HALError(f"Failed going to position {where}.")

        return command.info(text=f"At position {where}.")
예제 #6
0
파일: apogee.py 프로젝트: sdss/HAL
    async def expose_dither_pair(
        self,
        command: HALCommandType,
        exp_time: float,
        dither_sequence: str | None = None,
        exp_type: str = "object",
    ):
        """Takes an APOGEE dither set.

        Parameters
        ----------
        command
            The command used to interact with the APOGEE actor.
        exp_time
            The exposure time for each exposure in the dither sequence.
        exp_type
            The exposure type. Valid values are ``object``, ``dark``, and ``flat``.
        dither_sequence
            The dither sequence. If `None` the first dither will be taken at the
            current position and the mechanism will be switched after it.
            Alternatively, a string ``"AB"``, ``"BA"``, etc.

        """

        if dither_sequence is None:
            current = self.get_dither_position()
            if current is None:
                raise HALError(
                    "Cannot determine current APOGEE dither position.")
            dither_sequence = current.upper()
            dither_sequence = "AB" if dither_sequence == "A" else "BA"
        else:
            dither_sequence = dither_sequence.upper()
            if dither_sequence not in ["AB", "BA", "AA", "BB"]:
                raise HALError(f"Invalid dither sequence {dither_sequence}.")

        for dither_position in dither_sequence:
            await self.expose(
                command,
                exp_time,
                exp_type=exp_type,
                dither_position=dither_position,
            )
예제 #7
0
    def list_status(self) -> dict[str, tuple[bool, bool, float, bool]]:
        """Returns a dictionary with the state of the lamps.

        For each lamp the first value in the returned tuple is whether the
        lamp has been commanded on. The second value is whether the lamps is
        actually on. The third value is how long since it changed states.
        The final value indicates whether the lamp has warmed up.

        """

        state = {}
        for lamp in self.LAMPS:
            commanded_key = f"{lamp}LampCommandedOn"
            commanded_on = self.actor.models["mcp"][commanded_key][0]
            if commanded_on is None:
                raise HALError(f"Failed getting {commanded_key}.")
            if lamp in ["wht", "UV"]:
                is_on = bool(commanded_on)
                state[lamp] = (is_on, is_on, 0.0, is_on)
            else:
                lamp_key = f"{lamp}Lamp"
                lamp_state = self.actor.models["mcp"][lamp_key]
                if any([lv is None for lv in lamp_state]):
                    raise HALError(f"Failed getting {lamp_key}.")
                last_seen = lamp_state.last_seen
                if sum(lamp_state.value) == 4:
                    lamp_state = True
                elif sum(lamp_state.value) == 0:
                    lamp_state = False
                else:
                    raise HALError(
                        f"Failed determining state for lamp {lamp}.")

                elapsed = time.time() - last_seen
                warmed = (elapsed >= self.WARMUP[lamp]
                          ) if bool(commanded_on) else False
                state[lamp] = (bool(commanded_on), lamp_state, elapsed, warmed)

        return state
예제 #8
0
파일: boss.py 프로젝트: sdss/HAL
    async def expose(
        self,
        command: HALCommandType,
        exp_time: float = 0.0,
        exp_type: str = "science",
        readout: bool = True,
        read_async: bool = False,
    ):
        """Exposes BOSS. If ``readout=False``, does not read the exposure."""

        if self.readout_pending is not False:
            raise HALError(
                "Cannot expose. The camera is exposing or a readout is pending."
            )

        timeout = (exp_time + config["timeouts"]["expose"] +
                   config["timeouts"]["boss_flushing"])

        command_parts = [f"exposure {exp_type}"]

        if exp_type != "bias":
            command_parts.append(f"itime={round(exp_time, 1)}")

        if readout is False or read_async is True:
            command_parts.append("noreadout")
        else:
            timeout += config["timeouts"]["boss_readout"]

        command_string = " ".join(command_parts)

        await self._send_command(command,
                                 "boss",
                                 command_string,
                                 time_limit=timeout)

        self.__readout_pending = True

        if readout is True and read_async is True:
            # We use a _send_command because readout cannot await on itself.
            self.__readout_task = asyncio.create_task(
                self._send_command(
                    command,
                    "boss",
                    "exposure readout",
                    time_limit=25.0 + config["timeouts"]["boss_readout"],
                ))
            return

        self.__readout_pending = not readout
예제 #9
0
    async def axis_stop(self, command: HALCommandType, axis: str = "") -> bool:
        """Issues an axis stop to the TCC."""

        axis_stop_cmd = await self._send_command(
            command,
            "tcc",
            f"axis stop {axis}".strip(),
            time_limit=30.0,
            raise_on_fail=False,
        )

        if axis_stop_cmd.status.did_fail:
            raise HALError(
                "Error: failed to cleanly stop telescope via tcc axis stop.")

        self.is_slewing = False
        return True
예제 #10
0
파일: boss.py 프로젝트: sdss/HAL
    async def readout(self, command: HALCommandType):
        """Reads a pending readout."""

        if self.readout_pending is False:
            raise HALError("No pending readout.")

        command.debug("Reading pending BOSS exposure.")

        if self.readout_pending and self.__readout_task:
            await self.__readout_task

        else:
            await self._send_command(
                command,
                "boss",
                "exposure readout",
                time_limit=25.0 + config["timeouts"]["boss_readout"],
            )

        self.clear_readout()
예제 #11
0
파일: apogee.py 프로젝트: sdss/HAL
    async def expose(
        self,
        command: HALCommandType,
        exp_time: float,
        exp_type: str = "dark",
        dither_position: str | None = None,
    ):
        """Exposes APOGEE.

        Parameters
        ----------
        command
            The command used to interact with the APOGEE actor.
        exp_time
            The exposure time.
        exp_type
            The exposure type. Valid values are ``object``, ``dark``, ``flat``,
            and ``DomeFlat``.
        dither_position
            The dither position. If `None`, uses the current position.

        """

        if exp_type.lower() not in ["object", "dark", "flat", "domeflat"]:
            raise HALError(f"Invalid exposure type {exp_type}.")

        if dither_position:
            await self.set_dither_position(command, dither_position)

        expose_command = await self._send_command(
            command,
            "apogee",
            f"expose time={exp_time:.1f} object={exp_type.lower()}",
            time_limit=exp_time + config["timeouts"]["expose"],
        )

        return expose_command
예제 #12
0
    async def do_slew(
        self,
        command,
        coords: dict[str, float],
        keep_offsets=False,
        offset=False,
    ) -> bool:
        """Correctly handle a slew command, given what parse_args had received."""

        tcc_model = self.actor.models["tcc"]

        # NOTE: TBD: We should limit which offsets are kept.
        keep_args = "/keep=(obj,arc,gcorr,calib,bore)" if keep_offsets else ""

        slew_cmd = None
        if not offset:

            if "ra" in coords and "dec" in coords and "rot" in coords:
                ra = coords["ra"]
                dec = coords["dec"]
                rot = coords["rot"]

                command.info(text="Slewing to (ra, dec, rot) == "
                             f"({ra:.4f}, {dec:.4f}, {rot:g})")
                if keep_args:
                    command.warning(text="keeping all offsets")

                slew_cmd = self._send_command(
                    command,
                    "tcc",
                    f"track {ra}, {dec} icrs /rottype=object/rotang={rot:g}"
                    f"/rotwrap=mid {keep_args}",
                    time_limit=config["timeouts"]["slew"],
                    raise_on_fail=False,
                )

            elif "az" in coords and "alt" in coords and "rot" in coords:
                alt = coords["alt"]
                az = coords["az"]
                rot = coords["rot"]

                command.info(text="Slewing to (az, alt, rot) == "
                             f"({az:.4f}, {alt:.4f}, {rot:.4f})")

                slew_cmd = self._send_command(
                    command,
                    "tcc",
                    f"track {az:f}, {alt:f} mount/rottype=mount/rotangle={rot:f}",
                    time_limit=config["timeouts"]["slew"],
                    raise_on_fail=False,
                )

            else:

                raise HALError("Not enough coordinates information provided.")

        else:

            if "alt" not in coords or "az" not in coords:
                raise HALError("Not alt/az offsets provided.")

            # In arcsec
            alt = coords["alt"] or 0.0
            az = coords["az"] or 0.0
            rot = coords["rot"] or 0.0

            command.info(text=f"Offseting alt={alt:.3f}, az={az:.3f}")

            slew_cmd = self._send_command(
                command,
                "tcc",
                f"offset guide {az/3600.:g},{alt/3600.:g},{rot/3600.:g} /computed",
                time_limit=config["timeouts"]["slew"],
                raise_on_fail=False,
            )

        # "tcc track" in the new TCC is only Done successfully when all requested
        # axes are in the "tracking" state. All other conditions mean the command
        # failed, and the appropriate axisCmdState and axisErrCode will be set.
        # However, if an axis becomes bad during the slew, the TCC will try to
        # finish it anyway, so we need to explicitly check for bad bits.

        try:
            self.is_slewing = True
            slew_cmd = await slew_cmd
        finally:
            self.is_slewing = False

        if slew_cmd.status.did_fail:
            str_axis_state = ",".join(tcc_model["axisCmdState"].value)
            str_axis_code = ",".join(tcc_model["axisErrCode"].value)
            command.error(
                f"tcc track command failed with axis states: {str_axis_state} "
                f"and error codes: {str_axis_code}")
            raise HALError(
                "Failed to complete slew: see TCC messages for details.")

        if self.axes_are_clear() is False:
            axis_bits = self.get_bad_axis_bits()
            command.error("TCC slew command ended with some bad bits set: "
                          "0x{:x},0x{:x},0x{:x}".format(*axis_bits))
            raise HALError(
                "Failed to complete slew: see TCC messages for details.")

        return True
예제 #13
0
    async def turn_lamp(
        self,
        command: HALCommandType,
        lamps: str | list[str],
        state: bool,
        turn_off_others: bool = False,
        force: bool = False,
    ):
        """Turns a lamp on or off.

        This routine always blocks until the lamps are on and warmed up. If
        you don't want to block, call it as a task.

        Parameters
        ----------
        command
            The command that issues the lamp switching.
        lamp
            Name of the lamp(s) to turn on or off.
        turn_off_others
            Turn off all other lamps.
        force
            If `True`, send the on/off command regardless of status.

        """

        if isinstance(lamps, str):
            lamps = [lamps]

        for lamp in lamps:
            if lamp not in self.LAMPS:
                raise HALError(f"Invalid lamp {lamp}.")

        status = self.list_status()

        tasks = []
        turn_off_tasks = []
        for ll in self.LAMPS:
            if ll in lamps:
                if force is False and status[ll][0] is state and status[ll][
                        -1] is True:
                    pass
                else:
                    tasks.append(self._command_one(command, ll, state))
            else:
                if turn_off_others is True:
                    if status[ll][0] is not False or force is True:
                        turn_off_tasks.append(
                            self._command_one(command, ll, False))

        if len(turn_off_tasks) > 0:
            await asyncio.gather(*turn_off_tasks)

        await asyncio.gather(*tasks)

        done_lamps: list[bool] = [False] * len(lamps)
        warmed: list[bool] = [False] * len(lamps)

        n_iter = 0
        while True:

            if all(done_lamps):
                if state is False:
                    command.info(f"Lamp(s) {','.join(lamps)} are off.")
                    return
                elif all(warmed):
                    command.info(
                        f"Lamp(s) {','.join(lamps)} are on and warmed up.")
                    return

            new_status = self.list_status()
            for i, lamp in enumerate(lamps):
                # Don't don't anything if we're already at the state.
                if done_lamps[i] and (state is False or warmed[i]):
                    continue

                # Index 1 is if the lamp is actually on/off, not only commanded.
                if new_status[lamp][1] is state:
                    done_lamps[i] = True

                if state is True:
                    # Index 2 is the elapsed time since it was completely on/off.
                    elapsed = new_status[lamp][2]
                    if elapsed >= self.WARMUP[lamp]:
                        command.debug(f"Lamp {lamp}: warm-up complete.")
                        warmed[i] = True
                    elif (n_iter % 5) == 0:
                        remaining = int(self.WARMUP[lamp] - elapsed)
                        command.debug(f"Warming up lamp {lamp}: "
                                      f"{remaining} s remaining.")

            await asyncio.sleep(1)
            n_iter += 1