commands.auto.pid_to_angle

  1# Native imports
  2from math import inf, pi
  3from typing import Callable
  4
  5# Internal imports
  6from config import OperatorRobotConfig
  7from constants import SwerveDriveConsts
  8from subsystem.drivetrain.swerve_drivetrain import SwerveDrivetrain
  9
 10# Third-party imports
 11from commands2 import Command
 12from wpimath import applyDeadband
 13from wpimath.controller import ProfiledPIDController
 14from wpimath.geometry import Rotation2d, Translation2d
 15from wpimath.kinematics import ChassisSpeeds
 16from wpimath.trajectory import TrapezoidProfile
 17
 18
 19class PIDAlignToTarget(Command):
 20    def __init__(
 21        self,
 22        drivetrain: SwerveDrivetrain,
 23        target_location: Callable[[], Translation2d] | None,
 24        velocity_vector_x: Callable[[], float],
 25        velocity_vector_y: Callable[[], float],
 26        field: Callable[[], bool],
 27        rotation_pid_config: tuple = OperatorRobotConfig.pid_to_pose_rotation_pid_profile,
 28        setpoint_tolerances: tuple = OperatorRobotConfig.pid_to_pose_setpoint_tolerances,
 29        alignment_angle: Rotation2d = Rotation2d.fromDegrees(0)
 30    ) -> None:
 31        """
 32        Align rotationally to a target location using PID. In this control system, the objective function is the
 33        absolute error between the current and goal omega value. This objective function is minimized
 34        until rotational error is within a given tolerance. The decision variable of the control system
 35        is rotational velocity of the drivetrain. The contraint is a trapezoidal motion profile for
 36        rotational motion, where slopes represent max acceleration and the plateaus represent max
 37        velocity.
 38
 39        Note that the robot will rotate to the target orientation as quickly as possible.
 40
 41        Args:
 42            drivetrain: the drivetrain subsystem that effects movement from current rotation to target rotation
 43            target_location: the desired always-blue location to which the robot's heading should align
 44            velocity_vector_x: live poll of the percentage of max translation velocity that the
 45                drivetrain should go in the X (+ forward-backward -) direction. Has
 46                domain [-1, 1]
 47            velocity_vector_y: live poll of the percentage of max translation velocity that the
 48                drivetrain should go in the Y (+ left-right -) direction. Has
 49                domain [-1, 1]
 50            field: live poll. if True, the drivetrain should move in field relative mode. If False,
 51                the robot should move in robot relative mode
 52            rotation_pid_config: the PID and trapezoidal profile constants for rotational motion
 53            setpoint_tolerances: the threshold for omega below which absolute
 54                rotational error is low enough for the robot to be considered arrived at the target rotation
 55            alignment_angle: which angle, CCW positive, offset the robot's base heading to align
 56                with the target
 57
 58        Returns:
 59            None: class initialization executed upon construction
 60        """
 61        super().__init__()
 62
 63        self.drivetrain = drivetrain
 64        self.target_location = target_location
 65        self.velocity_vector_x = velocity_vector_x
 66        self.velocity_vector_y = velocity_vector_y
 67        self.field = field
 68        self.rotation_pid = ProfiledPIDController(
 69            *rotation_pid_config[0:3], TrapezoidProfile.Constraints(*rotation_pid_config[3:5])
 70        )
 71        self.alignment_angle = alignment_angle
 72
 73        self.rotation_pid.enableContinuousInput(-pi, pi)
 74        self.rotation_pid.setTolerance(setpoint_tolerances[2])
 75
 76        self.target_rotation = None
 77
 78        self.addRequirements(self.drivetrain)
 79
 80    def execute(self) -> None:
 81        """
 82        If a target rotation is available, run the profiled PID control system to move from the
 83        current rotation to the target rotation. When no target is available, pass through
 84        driver translation inputs with zero rotation so the drivetrain is not left uncontrolled.
 85
 86        Returns:
 87            None: translational and rotational velocities are passed into the drivetrain's drive
 88                method, which runs the motors accordingly
 89        """
 90        target = self.target_location() if self.target_location else None
 91        if target is None:
 92            self.target_rotation = None
 93            self.drivetrain.drive(
 94                self.velocity_vector_x() * SwerveDriveConsts.maxTranslationMPS,
 95                self.velocity_vector_y() * SwerveDriveConsts.maxTranslationMPS,
 96                0,
 97                self.field()
 98            )
 99            return
100
101        self.target_rotation = (target - self.drivetrain.current_pose().translation()).angle()
102        current_rotation = self.drivetrain.current_pose().rotation()
103        offset_current_rotation = current_rotation + self.alignment_angle
104
105        rotation_error = (self.target_rotation - offset_current_rotation).radians()
106        rotation_output = -applyDeadband(self.rotation_pid.calculate(rotation_error, 0), 0.04, inf)
107
108        new_rotation_velocity = ChassisSpeeds.fromFieldRelativeSpeeds(0, 0, rotation_output, current_rotation)
109        self.drivetrain.drive(
110            self.velocity_vector_x() * SwerveDriveConsts.maxTranslationMPS,
111            self.velocity_vector_y() * SwerveDriveConsts.maxTranslationMPS,
112            new_rotation_velocity.omega,
113            self.field()
114        )
115
116    def end(self, interrupted: bool) -> None:
117        """
118        When this command is over, have the robot stop driving (set all velocities to zero).
119
120        Args:
121            interrupted: whether the command was terminated by way of interruption
122
123        Returns:
124            None: drivetrain subsystem will apply zeroed velocities to motors
125        """
126        self.drivetrain.stop_driving(apply_to_modules=True)
127
128    def isFinished(self) -> bool:
129        """
130        The command is considered complete when the absolute error of each coodinate is within tolerance.
131        If there is no target rotation for the robot to navigate to, this command instantly ends.
132
133        Returns:
134            True if the command is complete according to the definition above, False otherwise.
135        """
136        at_setpoints = self.rotation_pid.atSetpoint()
137        no_target = self.target_rotation is None
138        return at_setpoints or no_target
class PIDAlignToTarget(commands2.command.Command):
 20class PIDAlignToTarget(Command):
 21    def __init__(
 22        self,
 23        drivetrain: SwerveDrivetrain,
 24        target_location: Callable[[], Translation2d] | None,
 25        velocity_vector_x: Callable[[], float],
 26        velocity_vector_y: Callable[[], float],
 27        field: Callable[[], bool],
 28        rotation_pid_config: tuple = OperatorRobotConfig.pid_to_pose_rotation_pid_profile,
 29        setpoint_tolerances: tuple = OperatorRobotConfig.pid_to_pose_setpoint_tolerances,
 30        alignment_angle: Rotation2d = Rotation2d.fromDegrees(0)
 31    ) -> None:
 32        """
 33        Align rotationally to a target location using PID. In this control system, the objective function is the
 34        absolute error between the current and goal omega value. This objective function is minimized
 35        until rotational error is within a given tolerance. The decision variable of the control system
 36        is rotational velocity of the drivetrain. The contraint is a trapezoidal motion profile for
 37        rotational motion, where slopes represent max acceleration and the plateaus represent max
 38        velocity.
 39
 40        Note that the robot will rotate to the target orientation as quickly as possible.
 41
 42        Args:
 43            drivetrain: the drivetrain subsystem that effects movement from current rotation to target rotation
 44            target_location: the desired always-blue location to which the robot's heading should align
 45            velocity_vector_x: live poll of the percentage of max translation velocity that the
 46                drivetrain should go in the X (+ forward-backward -) direction. Has
 47                domain [-1, 1]
 48            velocity_vector_y: live poll of the percentage of max translation velocity that the
 49                drivetrain should go in the Y (+ left-right -) direction. Has
 50                domain [-1, 1]
 51            field: live poll. if True, the drivetrain should move in field relative mode. If False,
 52                the robot should move in robot relative mode
 53            rotation_pid_config: the PID and trapezoidal profile constants for rotational motion
 54            setpoint_tolerances: the threshold for omega below which absolute
 55                rotational error is low enough for the robot to be considered arrived at the target rotation
 56            alignment_angle: which angle, CCW positive, offset the robot's base heading to align
 57                with the target
 58
 59        Returns:
 60            None: class initialization executed upon construction
 61        """
 62        super().__init__()
 63
 64        self.drivetrain = drivetrain
 65        self.target_location = target_location
 66        self.velocity_vector_x = velocity_vector_x
 67        self.velocity_vector_y = velocity_vector_y
 68        self.field = field
 69        self.rotation_pid = ProfiledPIDController(
 70            *rotation_pid_config[0:3], TrapezoidProfile.Constraints(*rotation_pid_config[3:5])
 71        )
 72        self.alignment_angle = alignment_angle
 73
 74        self.rotation_pid.enableContinuousInput(-pi, pi)
 75        self.rotation_pid.setTolerance(setpoint_tolerances[2])
 76
 77        self.target_rotation = None
 78
 79        self.addRequirements(self.drivetrain)
 80
 81    def execute(self) -> None:
 82        """
 83        If a target rotation is available, run the profiled PID control system to move from the
 84        current rotation to the target rotation. When no target is available, pass through
 85        driver translation inputs with zero rotation so the drivetrain is not left uncontrolled.
 86
 87        Returns:
 88            None: translational and rotational velocities are passed into the drivetrain's drive
 89                method, which runs the motors accordingly
 90        """
 91        target = self.target_location() if self.target_location else None
 92        if target is None:
 93            self.target_rotation = None
 94            self.drivetrain.drive(
 95                self.velocity_vector_x() * SwerveDriveConsts.maxTranslationMPS,
 96                self.velocity_vector_y() * SwerveDriveConsts.maxTranslationMPS,
 97                0,
 98                self.field()
 99            )
100            return
101
102        self.target_rotation = (target - self.drivetrain.current_pose().translation()).angle()
103        current_rotation = self.drivetrain.current_pose().rotation()
104        offset_current_rotation = current_rotation + self.alignment_angle
105
106        rotation_error = (self.target_rotation - offset_current_rotation).radians()
107        rotation_output = -applyDeadband(self.rotation_pid.calculate(rotation_error, 0), 0.04, inf)
108
109        new_rotation_velocity = ChassisSpeeds.fromFieldRelativeSpeeds(0, 0, rotation_output, current_rotation)
110        self.drivetrain.drive(
111            self.velocity_vector_x() * SwerveDriveConsts.maxTranslationMPS,
112            self.velocity_vector_y() * SwerveDriveConsts.maxTranslationMPS,
113            new_rotation_velocity.omega,
114            self.field()
115        )
116
117    def end(self, interrupted: bool) -> None:
118        """
119        When this command is over, have the robot stop driving (set all velocities to zero).
120
121        Args:
122            interrupted: whether the command was terminated by way of interruption
123
124        Returns:
125            None: drivetrain subsystem will apply zeroed velocities to motors
126        """
127        self.drivetrain.stop_driving(apply_to_modules=True)
128
129    def isFinished(self) -> bool:
130        """
131        The command is considered complete when the absolute error of each coodinate is within tolerance.
132        If there is no target rotation for the robot to navigate to, this command instantly ends.
133
134        Returns:
135            True if the command is complete according to the definition above, False otherwise.
136        """
137        at_setpoints = self.rotation_pid.atSetpoint()
138        no_target = self.target_rotation is None
139        return at_setpoints or no_target

A state machine representing a complete action to be performed by the robot. Commands are run by the commands2.CommandScheduler, and can be composed into CommandGroups to allow users to build complicated multistep actions without the need to roll the state machine logic themselves.

Commands are run synchronously from the main robot loop; no multithreading is used, unless specified explicitly from the command implementation.

PIDAlignToTarget( drivetrain: subsystem.drivetrain.swerve_drivetrain.SwerveDrivetrain, target_location: Callable[[], wpimath.geometry._geometry.Translation2d] | None, velocity_vector_x: Callable[[], float], velocity_vector_y: Callable[[], float], field: Callable[[], bool], rotation_pid_config: tuple = (1.0, 0.0, 0.0, 6.283185307179586, 6.283185307179586), setpoint_tolerances: tuple = (0.25, 0.25, 0.17453292519943295), alignment_angle: wpimath.geometry._geometry.Rotation2d = Rotation2d(0.000000))
21    def __init__(
22        self,
23        drivetrain: SwerveDrivetrain,
24        target_location: Callable[[], Translation2d] | None,
25        velocity_vector_x: Callable[[], float],
26        velocity_vector_y: Callable[[], float],
27        field: Callable[[], bool],
28        rotation_pid_config: tuple = OperatorRobotConfig.pid_to_pose_rotation_pid_profile,
29        setpoint_tolerances: tuple = OperatorRobotConfig.pid_to_pose_setpoint_tolerances,
30        alignment_angle: Rotation2d = Rotation2d.fromDegrees(0)
31    ) -> None:
32        """
33        Align rotationally to a target location using PID. In this control system, the objective function is the
34        absolute error between the current and goal omega value. This objective function is minimized
35        until rotational error is within a given tolerance. The decision variable of the control system
36        is rotational velocity of the drivetrain. The contraint is a trapezoidal motion profile for
37        rotational motion, where slopes represent max acceleration and the plateaus represent max
38        velocity.
39
40        Note that the robot will rotate to the target orientation as quickly as possible.
41
42        Args:
43            drivetrain: the drivetrain subsystem that effects movement from current rotation to target rotation
44            target_location: the desired always-blue location to which the robot's heading should align
45            velocity_vector_x: live poll of the percentage of max translation velocity that the
46                drivetrain should go in the X (+ forward-backward -) direction. Has
47                domain [-1, 1]
48            velocity_vector_y: live poll of the percentage of max translation velocity that the
49                drivetrain should go in the Y (+ left-right -) direction. Has
50                domain [-1, 1]
51            field: live poll. if True, the drivetrain should move in field relative mode. If False,
52                the robot should move in robot relative mode
53            rotation_pid_config: the PID and trapezoidal profile constants for rotational motion
54            setpoint_tolerances: the threshold for omega below which absolute
55                rotational error is low enough for the robot to be considered arrived at the target rotation
56            alignment_angle: which angle, CCW positive, offset the robot's base heading to align
57                with the target
58
59        Returns:
60            None: class initialization executed upon construction
61        """
62        super().__init__()
63
64        self.drivetrain = drivetrain
65        self.target_location = target_location
66        self.velocity_vector_x = velocity_vector_x
67        self.velocity_vector_y = velocity_vector_y
68        self.field = field
69        self.rotation_pid = ProfiledPIDController(
70            *rotation_pid_config[0:3], TrapezoidProfile.Constraints(*rotation_pid_config[3:5])
71        )
72        self.alignment_angle = alignment_angle
73
74        self.rotation_pid.enableContinuousInput(-pi, pi)
75        self.rotation_pid.setTolerance(setpoint_tolerances[2])
76
77        self.target_rotation = None
78
79        self.addRequirements(self.drivetrain)

Align rotationally to a target location using PID. In this control system, the objective function is the absolute error between the current and goal omega value. This objective function is minimized until rotational error is within a given tolerance. The decision variable of the control system is rotational velocity of the drivetrain. The contraint is a trapezoidal motion profile for rotational motion, where slopes represent max acceleration and the plateaus represent max velocity.

Note that the robot will rotate to the target orientation as quickly as possible.

Args: drivetrain: the drivetrain subsystem that effects movement from current rotation to target rotation target_location: the desired always-blue location to which the robot's heading should align velocity_vector_x: live poll of the percentage of max translation velocity that the drivetrain should go in the X (+ forward-backward -) direction. Has domain [-1, 1] velocity_vector_y: live poll of the percentage of max translation velocity that the drivetrain should go in the Y (+ left-right -) direction. Has domain [-1, 1] field: live poll. if True, the drivetrain should move in field relative mode. If False, the robot should move in robot relative mode rotation_pid_config: the PID and trapezoidal profile constants for rotational motion setpoint_tolerances: the threshold for omega below which absolute rotational error is low enough for the robot to be considered arrived at the target rotation alignment_angle: which angle, CCW positive, offset the robot's base heading to align with the target

Returns: None: class initialization executed upon construction

drivetrain
target_location
velocity_vector_x
velocity_vector_y
field
rotation_pid
alignment_angle
target_rotation
def execute(self) -> None:
 81    def execute(self) -> None:
 82        """
 83        If a target rotation is available, run the profiled PID control system to move from the
 84        current rotation to the target rotation. When no target is available, pass through
 85        driver translation inputs with zero rotation so the drivetrain is not left uncontrolled.
 86
 87        Returns:
 88            None: translational and rotational velocities are passed into the drivetrain's drive
 89                method, which runs the motors accordingly
 90        """
 91        target = self.target_location() if self.target_location else None
 92        if target is None:
 93            self.target_rotation = None
 94            self.drivetrain.drive(
 95                self.velocity_vector_x() * SwerveDriveConsts.maxTranslationMPS,
 96                self.velocity_vector_y() * SwerveDriveConsts.maxTranslationMPS,
 97                0,
 98                self.field()
 99            )
100            return
101
102        self.target_rotation = (target - self.drivetrain.current_pose().translation()).angle()
103        current_rotation = self.drivetrain.current_pose().rotation()
104        offset_current_rotation = current_rotation + self.alignment_angle
105
106        rotation_error = (self.target_rotation - offset_current_rotation).radians()
107        rotation_output = -applyDeadband(self.rotation_pid.calculate(rotation_error, 0), 0.04, inf)
108
109        new_rotation_velocity = ChassisSpeeds.fromFieldRelativeSpeeds(0, 0, rotation_output, current_rotation)
110        self.drivetrain.drive(
111            self.velocity_vector_x() * SwerveDriveConsts.maxTranslationMPS,
112            self.velocity_vector_y() * SwerveDriveConsts.maxTranslationMPS,
113            new_rotation_velocity.omega,
114            self.field()
115        )

If a target rotation is available, run the profiled PID control system to move from the current rotation to the target rotation. When no target is available, pass through driver translation inputs with zero rotation so the drivetrain is not left uncontrolled.

Returns: None: translational and rotational velocities are passed into the drivetrain's drive method, which runs the motors accordingly

def end(self, interrupted: bool) -> None:
117    def end(self, interrupted: bool) -> None:
118        """
119        When this command is over, have the robot stop driving (set all velocities to zero).
120
121        Args:
122            interrupted: whether the command was terminated by way of interruption
123
124        Returns:
125            None: drivetrain subsystem will apply zeroed velocities to motors
126        """
127        self.drivetrain.stop_driving(apply_to_modules=True)

When this command is over, have the robot stop driving (set all velocities to zero).

Args: interrupted: whether the command was terminated by way of interruption

Returns: None: drivetrain subsystem will apply zeroed velocities to motors

def isFinished(self) -> bool:
129    def isFinished(self) -> bool:
130        """
131        The command is considered complete when the absolute error of each coodinate is within tolerance.
132        If there is no target rotation for the robot to navigate to, this command instantly ends.
133
134        Returns:
135            True if the command is complete according to the definition above, False otherwise.
136        """
137        at_setpoints = self.rotation_pid.atSetpoint()
138        no_target = self.target_rotation is None
139        return at_setpoints or no_target

The command is considered complete when the absolute error of each coodinate is within tolerance. If there is no target rotation for the robot to navigate to, this command instantly ends.

Returns: True if the command is complete according to the definition above, False otherwise.