コード例 #1
0
ファイル: fps.py プロジェクト: sdss/jaeger
        async def set_rpm(activate):
            if activate:
                rpm = config["low_temperature"]["rpm_cold"]
                log.warning(f"Low temperature mode. Setting RPM={rpm}.")
            else:
                rpm = config["low_temperature"]["rpm_normal"]
                log.warning(
                    f"Disabling low temperature mode. Setting RPM={rpm}.")

            config["positioner"]["motor_speed"] = rpm
コード例 #2
0
ファイル: fps.py プロジェクト: sdss/jaeger
 async def set_idle_power(activate):
     if activate:
         ht = config["low_temperature"]["holding_torque_very_cold"]
         log.warning(
             "Very low temperature mode. Setting holding torque.")
     else:
         ht = config["low_temperature"]["holding_torque_normal"]
         log.warning(
             "Disabling very low temperature mode. Setting holding torque."
         )
     await self.send_command(
         CommandID.SET_HOLDING_CURRENT,
         alpha=ht[0],
         beta=ht[1],
     )
コード例 #3
0
ファイル: kaiju.py プロジェクト: sdss/jaeger
async def unwind(
    current_positions: dict[int, tuple[float | None, float | None]],
    collision_buffer: float | None = None,
    disabled: list[int] = [],
    force: bool = False,
):
    """Folds all the robots to the lattice position.

    This coroutine uses a process pool executor to run Kaiju routines.

    """

    alpha0, beta0 = config["kaiju"]["lattice_position"]

    # We create the data directly since it's simple. This should be a bit faster
    # than creating a grid and dumping it.
    data = {"collision_buffer": collision_buffer, "grid": {}}
    for pid, (alpha, beta) in current_positions.items():
        data["grid"][int(pid)] = (alpha, beta, alpha0, beta0, pid in disabled)

    (to_destination, _, did_fail, deadlocks) = await run_in_executor(
        get_path_pair,
        data=data,
        ignore_did_fail=force,
        stop_if_deadlock=force,
        executor="process",
    )
    if did_fail:
        if force is False:
            raise TrajectoryError(
                "Failed generating a valid unwind trajectory. "
                f"{len(deadlocks)} deadlocks were found ({deadlocks}).")
        else:
            log.warning("Deadlocks found in unwind but proceeding anyway.")

    return to_destination
コード例 #4
0
ファイル: fps.py プロジェクト: sdss/jaeger
    async def update_status(
        self,
        positioner_ids: Optional[int | List[int]] = None,
        timeout: float = 1,
    ) -> bool:
        """Update statuses for all positioners.

        Parameters
        ----------
        positioner_ids
            The list of positioners to update. If `None`, update all
            positioners.
        timeout
            How long to wait before timing out the command.

        """

        if len(self.positioners) == 0:
            return True

        if positioner_ids is None:
            positioner_ids = [0]
        elif not isinstance(positioner_ids, (list, tuple)):
            positioner_ids = [positioner_ids]

        if positioner_ids == [0]:
            n_positioners = len(self) if len(self) > 0 else None
        else:
            n_positioners = None

        await self.update_firmware_version(timeout=timeout)

        command = self.send_command(
            CommandID.GET_STATUS,
            positioner_ids=positioner_ids,
            n_positioners=n_positioners,
            timeout=timeout,
        )
        await command

        if command.status.failed:
            log.warning(
                f"{CommandID.GET_STATUS.name!r} failed during update status.")
            return False

        if len(command.replies) == 0:
            return True

        update_status_coros = []
        for pid, status_int in command.get_positioner_status().items(
        ):  # type: ignore

            if pid not in self:
                continue

            update_status_coros.append(self[pid].update_status(status_int))

        await asyncio.gather(*update_status_coros)

        # Set the status of the FPS based on positioner information.
        # First get the current bitmask without the status bit.
        current = self.status & ~FPSStatus.STATUS_BITS

        pbits = numpy.array([int(p.status) for p in self.values()])

        coll_bits = PositionerStatus.COLLISION_ALPHA | PositionerStatus.COLLISION_BETA

        if ((pbits & coll_bits) > 0).any():
            self.set_status(current | FPSStatus.COLLIDED)

        elif ((pbits & PositionerStatus.DISPLACEMENT_COMPLETED) > 0).all():
            self.set_status(current | FPSStatus.IDLE)

        else:
            self.set_status(current | FPSStatus.MOVING)

        return True
コード例 #5
0
ファイル: fps.py プロジェクト: sdss/jaeger
    async def _handle_temperature(self):
        """Handle positioners in low temperature."""

        if not isinstance(self.ieb, IEB):
            log.error("Cannot handle low-temperature mode. IEB not present.")

        async def set_rpm(activate):
            if activate:
                rpm = config["low_temperature"]["rpm_cold"]
                log.warning(f"Low temperature mode. Setting RPM={rpm}.")
            else:
                rpm = config["low_temperature"]["rpm_normal"]
                log.warning(
                    f"Disabling low temperature mode. Setting RPM={rpm}.")

            config["positioner"]["motor_speed"] = rpm

        async def set_idle_power(activate):
            if activate:
                ht = config["low_temperature"]["holding_torque_very_cold"]
                log.warning(
                    "Very low temperature mode. Setting holding torque.")
            else:
                ht = config["low_temperature"]["holding_torque_normal"]
                log.warning(
                    "Disabling very low temperature mode. Setting holding torque."
                )
            await self.send_command(
                CommandID.SET_HOLDING_CURRENT,
                alpha=ht[0],
                beta=ht[1],
            )

        sensor = config["low_temperature"]["sensor"]
        cold = config["low_temperature"]["cold_threshold"]
        very_cold = config["low_temperature"]["very_cold_threshold"]
        interval = config["low_temperature"]["interval"]

        while True:

            try:
                assert isinstance(self.ieb, IEB) and self.ieb.disabled is False
                device = self.ieb.get_device(sensor)
                temp = (await device.read())[0]

                # Get the status without the temperature bits.
                base_status = self.status & ~FPSStatus.TEMPERATURE_BITS

                if temp <= very_cold:
                    if self.status & FPSStatus.TEMPERATURE_NORMAL:
                        await set_rpm(True)
                        await set_idle_power(True)
                    elif self.status & FPSStatus.TEMPERATURE_COLD:
                        await set_idle_power(True)
                    else:
                        pass
                    self.set_status(base_status
                                    | FPSStatus.TEMPERATURE_VERY_COLD)

                elif temp <= cold:
                    if self.status & FPSStatus.TEMPERATURE_NORMAL:
                        await set_rpm(True)
                    elif self.status & FPSStatus.TEMPERATURE_COLD:
                        pass
                    else:
                        await set_idle_power(False)
                    self.set_status(base_status | FPSStatus.TEMPERATURE_COLD)

                else:
                    if self.status & FPSStatus.TEMPERATURE_NORMAL:
                        pass
                    elif self.status & FPSStatus.TEMPERATURE_COLD:
                        await set_rpm(False)
                    else:
                        await set_rpm(False)
                        await set_idle_power(False)
                    self.set_status(base_status | FPSStatus.TEMPERATURE_NORMAL)

            except BaseException as err:
                log.warning(
                    f"Cannot read device {sensor!r}. "
                    f"Low-temperature tracking temporarily disabled: {err}", )
                base_status = self.status & ~FPSStatus.TEMPERATURE_BITS
                self.set_status(base_status | FPSStatus.TEMPERATURE_UNKNOWN)

            finally:
                await asyncio.sleep(interval)
コード例 #6
0
    async def apply_correction(
        self,
        offsets: Optional[pandas.DataFrame] = None,
    ):  # pragma: no cover
        """Applies the offsets. Fails if the trajectory is collided or deadlock."""

        if self.fps.locked:
            raise FVCError("The FPS is locked. Cannot apply corrections.")

        self.log("Preparing correction trajectory.")

        if self.offsets is None and offsets is None:
            raise FVCError(
                "Offsets not set or passed. Cannot apply correction.")

        if offsets is None:
            offsets = self.offsets

        assert offsets is not None
        await self.fps.update_position()

        target_distance = config["fvc"]["target_distance"]

        # Setup robot grid.
        grid = get_robot_grid(self.fps)
        for robot in grid.robotDict.values():
            if robot.isOffline:
                continue

            positioner = self.fps[robot.id]
            robot.setAlphaBeta(positioner.alpha, positioner.beta)
            robot.setDestinationAlphaBeta(positioner.alpha, positioner.beta)

            if offsets.loc[robot.id].transformation_valid == 0:
                continue

            new = offsets.loc[robot.id, ["alpha_new", "beta_new"]]
            dist = offsets.loc[robot.id,
                               ["xwok_distance", "ywok_distance"]] * 1000.0
            if numpy.hypot(dist.xwok_distance,
                           dist.ywok_distance) > target_distance:
                robot.setDestinationAlphaBeta(new.alpha_new, new.beta_new)
            else:
                robot.isOffline = True

        # Check for collisions. If robots are collided just leave them there.
        collided = grid.getCollidedRobotList()
        n_coll = len(collided)
        if n_coll > 0:
            for pid in collided:
                positioner = self.fps[pid]
                alpha = positioner.alpha
                beta = positioner.beta
                grid.robotDict[pid].setAlphaBeta(alpha, beta)
                grid.robotDict[pid].setDestinationAlphaBeta(alpha, beta)

        # Generate trajectories.
        (to_destination, _, did_fail,
         deadlocks) = await get_path_pair_in_executor(
             grid,
             ignore_did_fail=True,
             stop_if_deadlock=True,
             ignore_initial_collisions=True,
         )
        if did_fail:
            log.warning(
                f"Found {len(deadlocks)} deadlocks but applying correction anyway."
            )

        self.log("Sending correction trajectory.")
        try:
            await self.fps.send_trajectory(to_destination,
                                           command=self.command)
        except TrajectoryError as err:
            raise FVCError(
                f"Failed executing the correction trajectory: {err}")

        self.correction_applied = True
        self.log("Correction applied.")
コード例 #7
0
ファイル: calibration.py プロジェクト: sdss/jaeger
async def calibrate_positioner(
    fps, positioner_id, motors=True, datums=True, cogging=True
):
    """Runs the calibration process and saves it to the internal memory.

    Parameters
    ----------
    fps : .FPS
        The instance of `.FPS` that will receive the trajectory.
    positioner_id : int
        The ID of the positioner to calibrate.
    motors : bool
        Whether to perform the motor calibration.
    datums : bool
        Whether to perform the datums calibration.
    cogging : bool
        Whether to perform the cogging calibration (may take more
        than one hour).

    Raises
    ------
    JaegerError
        If encounters a problem during the process.

    Examples
    --------
    ::

        >>> fps = FPS()
        >>> await fps.initialise()

        # Calibrate positioner 31.
        >>> await calibrate_positioner(fps, 31)

    """

    log.info(f"Calibrating positioner {positioner_id}.")

    if positioner_id not in fps.positioners:
        raise JaegerError(f"Positioner {positioner_id} not found.")

    positioner = fps[positioner_id]

    if fps.pollers.running:
        log.debug("Stopping pollers")
        await fps.pollers.stop()

    if motors:
        log.info("Starting motor calibration.")

        cmd = await fps.send_command(
            CommandID.START_MOTOR_CALIBRATION,
            positioner_ids=positioner_id,
        )

        if cmd.status.failed:
            raise JaegerError("Motor calibration failed.")

        await asyncio.sleep(1)
        await positioner.wait_for_status(
            [
                PS.DISPLACEMENT_COMPLETED,
                PS.MOTOR_ALPHA_CALIBRATED,
                PS.MOTOR_BETA_CALIBRATED,
            ]
        )
    else:
        log.warning("Skipping motor calibration.")

    if datums:
        log.info("Starting datum calibration.")
        cmd = await fps.send_command(
            CommandID.START_DATUM_CALIBRATION,
            positioner_ids=positioner_id,
        )
        if cmd.status.failed:
            raise JaegerError("Datum calibration failed.")

        await asyncio.sleep(1)
        await positioner.wait_for_status(
            [
                PS.DISPLACEMENT_COMPLETED,
                PS.DATUM_ALPHA_CALIBRATED,
                PS.DATUM_BETA_CALIBRATED,
            ]
        )
    else:
        log.warning("Skipping datum calibration.")

    if cogging:
        log.info("Starting cogging calibration.")
        cmd = await fps.send_command(
            CommandID.START_COGGING_CALIBRATION,
            positioner_ids=positioner_id,
        )
        if cmd.status.failed:
            raise JaegerError("Cogging calibration failed.")

        await asyncio.sleep(1)
        await positioner.wait_for_status(
            [PS.COGGING_ALPHA_CALIBRATED, PS.COGGING_BETA_CALIBRATED]
        )
    else:
        log.warning("Skipping cogging calibration.")

    if motors or datums or cogging:
        log.info("Saving calibration.")
        cmd = await fps.send_command(
            CommandID.SAVE_INTERNAL_CALIBRATION,
            positioner_ids=positioner_id,
        )
        if cmd.status.failed:
            raise JaegerError("Saving calibration failed.")

        log.info(f"Positioner {positioner_id} has been calibrated.")

    return
コード例 #8
0
async def create_random_configuration(
    fps: FPS,
    seed: int | None = None,
    safe=True,
    uniform: tuple[float, ...] | None = None,
    collision_buffer: float | None = None,
    max_deadlocks: int = 6,
    deadlock_retries: int = 5,
    **kwargs,
):
    """Creates a random configuration using Kaiju."""

    from jaeger.target.configuration import ManualConfiguration

    seed = seed or numpy.random.randint(0, 1000000)
    numpy.random.seed(seed)

    robot_grid = get_robot_grid(fps,
                                seed=seed,
                                collision_buffer=collision_buffer)

    alphaL, betaL = config["kaiju"]["lattice_position"]

    # We use Kaiju for convenience in the non-safe mode.
    for robot in robot_grid.robotDict.values():

        if robot.isOffline:
            continue

        if uniform is not None:
            alpha0, alpha1, beta0, beta1 = uniform
            robot.setAlphaBeta(
                numpy.random.uniform(alpha0, alpha1),
                numpy.random.uniform(beta0, beta1),
            )

        else:
            if safe:
                safe_mode = config["safe_mode"]
                if safe_mode is False:
                    safe_mode = {"min_beta": 165, "max_beta": 175}

                robot.setAlphaBeta(
                    numpy.random.uniform(0, 359.9),
                    numpy.random.uniform(
                        safe_mode["min_beta"],
                        175.0,
                    ),
                )

            else:
                robot.setXYUniform()

        robot.setDestinationAlphaBeta(alphaL, betaL)

    # Confirm that the configuration is valid. This should only matter
    # for full range random configurations.
    try:
        robot_grid, _ = await decollide_in_executor(robot_grid, simple=True)
        grid_data = {
            robot.id: (robot.alpha, robot.beta)
            for robot in robot_grid.robotDict.values()
        }
    except JaegerError:
        raise JaegerError(
            "Decollision failed. Cannot create random configuration.")

    _, _, did_fail, deadlocks = await get_path_pair_in_executor(robot_grid)

    # If too many deadlocks, just try a new seed.
    n_deadlock = len(deadlocks)
    if did_fail and n_deadlock > max_deadlocks:
        log.warning("Too many deadlocked robots. Trying new seed.")
        return await create_random_configuration(
            fps,
            safe=safe,
            uniform=uniform,
            collision_buffer=collision_buffer,
            deadlock_retries=deadlock_retries,
        )

    if did_fail and n_deadlock > 0:
        # Now the fun part, if there are only a few deadlocks, try assigning them
        # a random position.
        log.warning(f"Found {n_deadlock} deadlocked robots. Trying to unlock.")
        for nn in range(1, deadlock_retries + 1):
            log.info(f"Retry {nn} out of {deadlock_retries}.")

            to_replace_robot = numpy.random.choice(deadlocks)

            robot_grid = get_robot_grid(
                fps,
                seed=seed + 1,
                collision_buffer=collision_buffer,
            )

            for robot in robot_grid.robotDict.values():

                if robot.isOffline:
                    continue

                if robot.id == to_replace_robot:
                    robot.setXYUniform()
                else:
                    robot.setAlphaBeta(*grid_data[robot.id])

            try:
                robot_grid, _ = await decollide_in_executor(robot_grid,
                                                            simple=True)
                grid_data = {
                    robot.id: (robot.alpha, robot.beta)
                    for robot in robot_grid.robotDict.values()
                }
            except JaegerError:
                raise JaegerError(
                    "Decollision failed. Cannot create random configuration.")

            _, _, did_fail, deadlocks = await get_path_pair_in_executor(
                robot_grid)
            if did_fail is False:
                log.info("Random configuration has been unlocked.")
                break
            else:
                log.info(f"{len(deadlocks)} deadlocks remaining.")

            if nn == deadlock_retries:
                log.warning("Failed unlocking. Trying new seed.")
                return await create_random_configuration(
                    fps,
                    seed=seed + 1,
                    safe=safe,
                    uniform=uniform,
                    collision_buffer=collision_buffer,
                    deadlock_retries=deadlock_retries,
                )

    pT = calibration.positionerTable.copy().reset_index()

    # Build an assignment dictionary.
    data = {}
    for pid in grid_data:
        holeID = pT.loc[pT.positionerID == pid].holeID.values[0]
        data[holeID] = {
            "alpha": grid_data[pid][0],
            "beta": grid_data[pid][1],
            "fibre_type": "Metrology",
        }

    return ManualConfiguration(data, **kwargs)