Example #1
0
    def reset(self, chassis, gyro):
        '''
        Re-initializes all of the data in the controller as if the path has not yet been executed.
        This method MUST be called in teleopInit and autonomousInit directly before the controller is used.

        reset(self, chassis: traits.DriveTrain, gyro: traits.Gyro)

        :param chassis: An object that implements the DriveTrain trait. This object's encoders will be reset by this function
        :param gyro: An object that implements the Gyro trait. This object's angle will be reset by this function
        '''

        # Assert the objects implement the proper traits
        assert chassis.implements(DriveTrain)
        assert gyro.implements(Gyro)

        # The encoders and gyro need to be reset so that the Ramsete
        # controller is fed data that looks new.
        # By reseting these sensors, we look like weve never run the path at all!
        chassis.reset_encoders()
        gyro.reset()
        self.are_wheel_speeds_zero = False

        # The odometry object also needs to be re-initialized
        # so that it forgets the state from the previous run
        self.odometry = DifferentialDriveOdometry(
            Rotation2d(radians(0)), self.trajectory.initialPose())

        self.timer.start()
Example #2
0
    def __init__(self, kS, kV, trackwidth, trajectory):
        '''
        Creates a controller for following a PathWeaver trajectory.

        __init__(self, kS: Volts, kV: Volts * Seconds / Meters, trackwidth: Meters, trajectory: wpilib.trajectory.Trajectory)

        :param kS: The kS gain determined by characterizing the Robot's drivetrain
        :param kV: The kV gain determined by characterizing the Robot's drivetrain
        :param trackwidth: The horizontal distance between the left and right wheels of the tank drive.
        :param trajectory: The trajectory to follow. This can be generated by PathWeaver, or made by hand.
        '''

        self.kS = kS
        self.kV = kV

        self.trajectory = trajectory

        self.odometry = DifferentialDriveOdometry(
            Rotation2d(radians(0)), self.trajectory.initialPose())

        self.ramsete = RamseteController(2, 0.7)
        self.drive_kinematics = DifferentialDriveKinematics(trackwidth)

        self.are_wheel_speeds_zero = False
        self.timer = Timer()
Example #3
0
    def robotInit(self):
        """Robot initialization function"""
        self.frontLeftMotor = wpilib.Talon(self.frontLeftChannel)
        self.rearLeftMotor = wpilib.Talon(self.rearLeftChannel)
        self.frontRightMotor = wpilib.Talon(self.frontRightChannel)
        self.rearRightMotor = wpilib.Talon(self.rearRightChannel)

        self.lstick = wpilib.Joystick(self.lStickChannel)
        self.rstick = wpilib.Joystick(self.rStickChannel)

        # Position gets automatically updated as robot moves
        self.gyro = wpilib.AnalogGyro(1)

        self.sd = NetworkTables.getTable("SmartDashboard")
        # Setting up Kinematics (an algorithm to determine chassi speed from wheel speed)
        # The 2d Translation tells the algorithm how far off center (in Meter) our wheels are
        # Ours are about 11.3 (x) by 10.11(y) inches off, so this equates to roughly .288 X .257 Meters
        # We use the X and Y Offsets above.

        m_frontLeftLocation = Translation2d(XOffset, YOffset)
        m_frontRightLocation = Translation2d(XOffset, (-1 * YOffset))
        m_backLeftLocation = Translation2d((-1 * XOffset), (YOffset))
        m_backRightLocation = Translation2d((-1 * XOffset), (-1 * YOffset))

        # Creat our kinematics object using the wheel locations.
        self.m_kinematics = MecanumDriveKinematics(
            m_frontLeftLocation,
            m_frontRightLocation,
            m_backLeftLocation,
            m_backRightLocation,
        )
        # Create the Odometry Object
        self.MecanumDriveOdometry = MecanumDriveOdometry(
            self.m_kinematics,
            Rotation2d.fromDegrees(-self.gyro.getAngle()),
            Pose2d(0, 0, Rotation2d(0)),
        )

        self.drive = MecanumDrive(
            self.frontLeftMotor,
            self.rearLeftMotor,
            self.frontRightMotor,
            self.rearRightMotor,
        )
        self.f_l_encoder = wpilib.Encoder(0, 1)
        self.f_l_encoder.setDistancePerPulse(
            (math.pi * self.WHEEL_DIAMETER) / self.ENCODER_COUNTS_PER_REV)

        self.r_l_encoder = wpilib.Encoder(3, 4)
        self.r_l_encoder.setDistancePerPulse(
            (math.pi * self.WHEEL_DIAMETER) / self.ENCODER_COUNTS_PER_REV)

        self.f_r_encoder = wpilib.Encoder(1, 2)
        self.f_r_encoder.setDistancePerPulse(
            (math.pi * self.WHEEL_DIAMETER) / self.ENCODER_COUNTS_PER_REV)

        self.r_r_encoder = wpilib.Encoder(3, 4)
        self.r_r_encoder.setDistancePerPulse(
            (math.pi * self.WHEEL_DIAMETER) / self.ENCODER_COUNTS_PER_REV)
Example #4
0
    def setup_trajectory(self, trajectory: Trajectory):
        self.controller = RamseteController()
        self.left_controller.reset()
        self.right_controller.reset()
        self.prev_time = 0
        self.speed = DifferentialDriveWheelSpeeds()

        self.controller.setTolerance(
            Pose2d(0.05, 0.05, Rotation2d(math.radians(5))))
        self.initial_state = self.trajectory.sample(0)
        speeds = ChassisSpeeds()
        speeds.vx = self.initial_state.velocity
        speeds.omega = self.initial_state.velocity * self.initial_state.curvature
        self.prevSpeeds = self.kinematics.toWheelSpeeds(speeds)
Example #5
0
    def follow(self, chassis, gyro):
        '''
        This updates the Path and drives the chassis to follow the path

        update(self, chassis: traits.DriveTrain, gyro: traits.Gyro)

        :param chassis: An object that implements the DriveTrain trait. When this function is called,
                        the object's motors will be driven to follow the last set trajectory.
        :param gyro: An object that implements the gyro trait.
        '''
        # Assert the objects implement the proper traits
        assert chassis.implements(DriveTrain)
        assert gyro.implements(Gyro)

        if self.is_done():
            return

        # Set the chassis to low gear for more precise movements
        chassis.set_low_gear()

        # If a trajectory has been set, run it
        if self.trajectory is not None:
            # Get the accumulated left and right distance of the chass
            ld, rd = chassis.get_left_distance(), chassis.get_right_distance()

            # Ramsete requires the counterclockwise angle of the Robot
            angle = gyro.get_counterclockwise_degrees()

            # Get the current position of the robot
            current_pose = self.odometry.update(Rotation2d(radians(angle)), ld,
                                                rd)

            # Calculate the target position using the trajectory, and get the chassis wheel speeds
            target_pose = self.trajectory.sample(self.timer.get())
            chassis_speed = self.ramsete.calculate(current_pose, target_pose)
            wheel_speeds = self.drive_kinematics.toWheelSpeeds(chassis_speed)
            l, r = wheel_speeds.left, wheel_speeds.right

            if abs(l) == 0 and abs(r) == 0:
                self.are_wheel_speeds_zero = True

            # Convert the left and right wheel speeds to volts using the characterized constants,
            # and then convert those to percent values from -1 to 1
            chassis.tank_drive((self.kS + l * self.kV) / 12,
                               (self.kS + r * self.kV) / 12)
Example #6
0
    def execute(self):
        dt = 0.02
        self.wheel_left.update(self.dm_l.getPosition(), dt)
        self.wheel_right.update(self.dm_r.getPosition(), dt)
        if wpilib.RobotBase.isSimulation():
            self.wheel_left.position = self._getSimulationPosition(1)
            self.wheel_right.position = self._getSimulationPosition(3)

        self.odometry.update(
            Rotation2d(self.getHeading()),
            self.wheel_left.position,
            self.wheel_right.position,
        )

        if self.mode == self._Mode.Idle:
            self.dm_l.setOutput(0)
            self.dm_r.setOutput(0)
        elif self.mode == self._Mode.PercentOutput:
            self.dm_l.setOutput(self.desired_output.left)
            self.dm_r.setOutput(self.desired_output.right)
        elif self.mode == self._Mode.Velocity:
            self.feedforward.left = (
                self.feedforward_l.calculate(self.desired_velocity.left, dt) /
                12)

            self.feedforward.right = (
                self.feedforward_r.calculate(self.desired_velocity.right, dt) /
                12)
            if not wpilib.RobotBase.isSimulation():
                self.dm_l.setVelocity(
                    self.desired_velocity.left,
                    self.feedforward.left,
                )
                self.dm_r.setVelocity(
                    self.desired_velocity.right,
                    self.feedforward.right,
                )
            else:
                self.dm_l.setOutput(self.desired_velocity.left /
                                    self.MAX_VELOCITY)
                self.dm_r.setOutput(self.desired_velocity.right /
                                    self.MAX_VELOCITY)
        self.updateNetworkTables()
def test_swerve4_odometry(s4: SwerveDrive4Kinematics):
    odometry = SwerveDrive4Odometry(s4, Rotation2d(0))
    odometry.resetPosition(Pose2d(), Rotation2d.fromDegrees(90))

    state = SwerveModuleState(5)

    odometry.updateWithTime(
        0,
        Rotation2d.fromDegrees(90),
        SwerveModuleState(),
        SwerveModuleState(),
        SwerveModuleState(),
        SwerveModuleState(),
    )

    pose = odometry.updateWithTime(0.1, Rotation2d.fromDegrees(90), state,
                                   state, state, state)

    assert pose.translation().x == pytest.approx(0.5)
    assert pose.translation().y == pytest.approx(0.0)
    assert pose.rotation().degrees() == pytest.approx(0)
    def setup(self) -> None:
        self.left_front.setInverted(False)
        self.right_front.setInverted(True)

        self.disable_brake_mode()

        self.left_rear.follow(self.left_front)
        self.right_rear.follow(self.right_front)

        self.left_encoder = rev.CANEncoder(self.left_front)
        self.right_encoder = rev.CANEncoder(self.right_front)

        rev_to_m = WHEEL_CIRCUMFERENCE / GEAR_RATIO

        for enc in (self.left_encoder, self.right_encoder):
            enc.setPositionConversionFactor(rev_to_m)
            enc.setVelocityConversionFactor(rev_to_m / 60)

        self.left_pid: rev.CANPIDController = self.left_front.getPIDController(
        )
        self.right_pid: rev.CANPIDController = self.right_front.getPIDController(
        )

        self.ff_calculator = SimpleMotorFeedforwardMeters(kS=0.194,
                                                          kV=2.79,
                                                          kA=0.457)
        for pid in (self.left_pid, self.right_pid):
            # TODO: needs tuning
            pid.setP(5.19 / 10)
            pid.setI(0)
            pid.setD(0)
            pid.setIZone(0)
            pid.setFF(0)
            pid.setOutputRange(-1, 1)

        self.kinematics = DifferentialDriveKinematics(TRACK_WIDTH)
        self.odometry = DifferentialDriveOdometry(self._get_heading())

        # default_position on the field
        self.reset_odometry(Pose2d(-3, 0, Rotation2d(math.pi)))
Example #9
0
@dataclass
class TrajectoryData:
    """Hold metadata for a wpilib trajectory"""
    start: Pose2d
    interior_waypoints: List[Translation2d]
    end: Pose2d
    config: TrajectoryConfig = TrajectoryConfig(MAX_GENERATION_VELOCITY,
                                                MAX_GENERATION_ACCELERATION)
    constraints: Tuple[TrajectoryConstraint, ...] = DEFAULT_CONSTRAINTS
    kinematics: DifferentialDriveKinematics = KINEMATICS
    field_relative: bool = True
    reverse: bool = False


# The POWER PORT's Pose relative to the top left coordinate of the field as provided by Path weaver.
POWER_PORT = Pose2d(0.2, -2.437, Rotation2d())


# Starting positions are relative to the field's top left coordinate facing down the field
class StartingPosition(Enum):
    # LEFT is one foot off the LEFT wall relative to the Power port (and facing the power port).
    LEFT = Pose2d(3.2, -0.6468, Rotation2d.fromDegrees(180))
    CENTER = Pose2d(3.2, 0, Rotation2d.fromDegrees(180)).relativeTo(POWER_PORT)
    RIGHT = Pose2d(3.2, -3.25,
                   Rotation2d.fromDegrees(180)).relativeTo(POWER_PORT)


# ALL trajectory points should be relative to the field's top left coordinate down the field
# Trajectories that aren't field relative are generated with respect to ALL Starting Positions
# E.x. "charge-LEFT", "charge-CENTER", "charge-RIGHT"
# We do this to allow for trajectory chaining, which requires field-relativity
Example #10
0
 def find_field_angle(self, field_point: Translation2d) -> float:
     """Return the theoretical angle (in radians) to the target based on odometry"""
     pose = self.get_pose()
     rel = field_point - pose.translation()
     rel_heading = Rotation2d(rel.y, rel.x) + pose.rotation()
     return rel_heading.radians()
Example #11
0
 def _get_heading(self) -> Rotation2d:
     """Get the current heading of the robot from the IMU, anticlockwise positive."""
     return Rotation2d(self.imu.getYaw())
Example #12
0
    def robotInit(self):
        """Robot initialization function"""
        self.frontLeftMotor = wpilib.Talon(self.frontLeftChannel)
        self.rearLeftMotor = wpilib.Talon(self.rearLeftChannel)
        self.frontRightMotor = wpilib.Talon(self.frontRightChannel)
        self.rearRightMotor = wpilib.Talon(self.rearRightChannel)

        self.lstick = wpilib.Joystick(self.lStickChannel)
        self.rstick = wpilib.Joystick(self.rStickChannel)
        self.sd = NetworkTables.getTable("SmartDashboard")

        # Position gets automatically updated as robot moves
        self.gyro = wpilib.AnalogGyro(1)

        #Create the DriveTrain
        self.drive = MecanumDrive(
            self.frontLeftMotor,
            self.rearLeftMotor,
            self.frontRightMotor,
            self.rearRightMotor,
        )
        #Create The Encoders
        self.f_l_encoder = wpilib.Encoder(0, 1)
        self.f_l_encoder.setDistancePerPulse((.0254 * 6 * math.pi) / 1024)
        #self.f_l_encoder.setSimDevice(0)

        self.r_l_encoder = wpilib.Encoder(3, 4)
        self.r_l_encoder.setDistancePerPulse((.0254 * 6 * math.pi) / 1024)
        #self.r_l_encoder.setSimDevice(1)

        self.f_r_encoder = wpilib.Encoder(1, 2)
        self.f_r_encoder.setDistancePerPulse((.0254 * 6 * math.pi) / 1024)
        #self.f_r_encoder.setSimDevice(2)

        self.r_r_encoder = wpilib.Encoder(3, 4)
        self.r_r_encoder.setDistancePerPulse((.0254 * 6 * math.pi) / 1024)
        #self.r_r_encoder.setSimDevice(3)

        # Setting up Kinematics (an algorithm to determine chassi speed from wheel speed)
        # The 2d Translation tells the algorithm how far off center (in Meter) our wheels are
        # Ours are about 11.3 (x) by 10.11(y) inches off, so this equates to roughly .288 X .257 Meters
        # We use the X and Y Offsets above.

        m_frontLeftLocation = Translation2d(XOffset, YOffset)
        m_frontRightLocation = Translation2d(XOffset, (-1 * YOffset))
        m_backLeftLocation = Translation2d((-1 * XOffset), (YOffset))
        m_backRightLocation = Translation2d((-1 * XOffset), (-1 * YOffset))

        # Creat our kinematics object using the wheel locations.
        self.m_kinematics = MecanumDriveKinematics(
            m_frontLeftLocation,
            m_frontRightLocation,
            m_backLeftLocation,
            m_backRightLocation,
        )
        # Create the Odometry Object
        self.MecanumDriveOdometry = MecanumDriveOdometry(
            self.m_kinematics,
            Rotation2d.fromDegrees(-self.gyro.getAngle()),
            Pose2d(0, 0, Rotation2d(0)),
        )

        # Now that we have created the ability to see wheel speeds, chassis speeds and our position
        # Let us start to use feedforward to try to lock our robot into a specific speed.
        self.feedForward = SimpleMotorFeedforwardMeters(kS=0.194,
                                                        kV=.5,
                                                        kA=0.457)
Example #13
0
 def execute(self):
     self.odometry.update(Rotation2d(math.radians(self.getAngle())),
                          self.left_encoder.getPosition(),
                          -self.right_encoder.getPosition())
     self.left_distance = self.left_encoder.getPosition()
     self.right_distance = -self.right_encoder.getPosition()
Example #14
0
 def setup(self):
     self.odometry = DifferentialDriveOdometry(
         Rotation2d(math.radians(self.getAngle())))