Robot Not Following Given Path (Trajectory Following/PathWeaver)

I’ve been following the tutorial for WPILib’s trajectory following in a command-based Java structure (steps for trajectory following found here and method of integrating PathWeaver found here ). After setting everything up as shown and deploying to the robot, the path given is not followed by the bot, with (apparently) little consistency between attempts. For example, feeding in a straight path resulted in the robot veering slightly right on one enable, and attempting a full turn on the next.

Here’s the code for RobotContainer and the actual command:
/----------------------------------------------------------------------------/

/* Copyright © 2018-2019 FIRST. All Rights Reserved. */

/* Open Source Software - may be modified and shared by FRC teams. The code */

/* must be accompanied by the FIRST BSD license file in the root directory of */

/* the project. */

/----------------------------------------------------------------------------/

package frc.robot;

import java.io.IOException;

import java.nio.file.Paths;

// import java.util.List;

import edu.wpi.first.wpilibj.GenericHID;

import edu.wpi.first.wpilibj.Joystick;

import edu.wpi.first.wpilibj.XboxController;

import edu.wpi.first.wpilibj.controller.PIDController;

import edu.wpi.first.wpilibj.controller.RamseteController;

import edu.wpi.first.wpilibj.controller.SimpleMotorFeedforward;

import edu.wpi.first.wpilibj.geometry.Pose2d;

import edu.wpi.first.wpilibj.geometry.Rotation2d;

import edu.wpi.first.wpilibj.geometry.Translation2d;

import edu.wpi.first.wpilibj.trajectory.Trajectory;

// import edu.wpi.first.wpilibj.trajectory.TrajectoryConfig;

import edu.wpi.first.wpilibj.trajectory.TrajectoryUtil;

// import edu.wpi.first.wpilibj.trajectory.constraint.DifferentialDriveVoltageConstraint;

import edu.wpi.first.wpilibj2.command.Command;

import edu.wpi.first.wpilibj2.command.RamseteCommand;

import frc.robot.Constants.CharacterizationConstants;

import frc.robot.commands.ArcadeDrive;

import frc.robot.subsystems.DriveSubsystem;

/**

  • This class is where the bulk of the robot should be declared. Since

  • Command-based is a “declarative” paradigm, very little robot logic should

  • actually be handled in the {@link Robot} periodic methods (other than the

  • scheduler calls). Instead, the structure of the robot (including subsystems,

  • commands, and button mappings) should be declared here.

*/

public class RobotContainer {

// The robot’s subsystems and commands are defined here…

DriveSubsystem driveSubsystem = new DriveSubsystem();

public static Joystick controller;

/**

  • The container for the robot. Contains subsystems, OI devices, and commands.

*/

public RobotContainer() {

// Configure the button bindings

driveSubsystem.setDefaultCommand(new ArcadeDrive(driveSubsystem));

configureButtonBindings();

}

/**

  • Use this method to define your button->command mappings. Buttons can be

  • created by instantiating a {@link GenericHID} or one of its subclasses

  • ({@link edu.wpi.first.wpilibj.Joystick} or {@link XboxController}), and then

  • passing it to a {@link edu.wpi.first.wpilibj2.command.button.JoystickButton}.

*/

private void configureButtonBindings() {

controller = new Joystick(0);

}

/**

  • Use this to pass the autonomous command to the main {@link Robot} class.

  • @return the command to run in autonomous

  • @throws IOException

*/

public Command getAutonomousCommand() throws IOException {

// // Create a voltage constraint to ensure we don't accelerate too fast

// var autoVoltageConstraint = new DifferentialDriveVoltageConstraint(

//     new SimpleMotorFeedforward(CharacterizationConstants.ksVolts, CharacterizationConstants.kvVoltSecondsPerMeter,

//         CharacterizationConstants.kaVoltSecondsSquaredPerMeter),

//     CharacterizationConstants.kDriveKinematics, 10);

Pose2d strikeIt = new Pose2d();

driveSubsystem.resetEncoders();

driveSubsystem.zeroHeading();

driveSubsystem.resetOdometry(strikeIt);

// Trajectory is read from Pathweaver .json file; place file in src/main/deploy

Trajectory exampleTrajectory = TrajectoryUtil.fromPathweaverJson(Paths.get("/home/lvuser/deploy/Straight2.wpilib.json")); 

RamseteCommand ramseteCommand = new RamseteCommand(exampleTrajectory, driveSubsystem::getPose,

    new RamseteController(CharacterizationConstants.kRamseteB, CharacterizationConstants.kRamseteZeta),

    new SimpleMotorFeedforward(CharacterizationConstants.ksVolts, CharacterizationConstants.kvVoltSecondsPerMeter,

        CharacterizationConstants.kaVoltSecondsSquaredPerMeter),

    CharacterizationConstants.kDriveKinematics,

    driveSubsystem::getWheelSpeeds,

    new PIDController(CharacterizationConstants.kPDriveVel, 0, 0), 

    new PIDController(CharacterizationConstants.kPDriveVel, 0, 0),

    // RamseteCommand passes volts to the callback

    driveSubsystem::tankDriveVolts, 

    driveSubsystem

);

// Run path following command, then stop at the end.

return ramseteCommand.andThen(() -> driveSubsystem.tankDriveVolts(0, 0));

}

}

We’re resetting everything properly as far as I know. We are using CANEncoders rather than the standard encoders used in the tutorial. Any help would be greatly appreciated- thank you.

Definitely a difficult to read post. My first suggestion would be to put your odometry and wheel speeds to Shuffleboard, to ensure everything is working as expected.

Do you have your code on Github or someplace where you can post a link or share? It’s tough to read and understand here.

Not entirely on topic, but watch out for that IOException thrown from getAutonomousCommand(). Make sure you’re catching that someplace so it doesn’t crash your robot.

RobotContainer.java (4.3 KB)
Here’s the same code in a Notepad file. If there’s a better way to format readabilty, I’d be glad to try it; the team Github is private. @gdefender Thanks for the heads up on the IOException; it is caught elsewhere.

A lot of the heavy lifting is in your DriveSubsystem. Is that where you have odometry implemented? Can you share that class too?

DriveSubsystem.java (6.7 KB)

One thing you might be missing is transforming the PathWeaver trajectory to match your current pose. Something like:

      var transform = driveSubsystem.getCurrentPose().minus(exampleTrajectory.getInitialPose());
      exampleTrajectory = straightTrajectory.transformBy(transform);

See Transforming Trajectories - The transformBy Method

2 Likes

Aha- that’s probably it. That would explain why the robot is veering off the assigned path, it doesn’t believe it’s at the start point yet (I think, if I’m understanding correctly). Thank you so much!

1 Like

I have a few notes for your DriveSubsystem.

Your conversion from encoder native units to meters doesn’t seem right to me, but we use Talons so I can’t say for sure. Looking at the documentation, it says the conversion factor is multiplied by the native units. I assume “native units” is edges, not revolutions. In your code, you’re setting the conversion factor to the circumference of your wheel, this should be divided by your EPS. I think the conversion factor should be the distance in meters per native unit (edge), not per revolution.

The same would apply to your velocity conversion factor. Your comment says the native scale is RPM, that’s ambiguous to me in REV’s documentation. It says the velocity conversion factor is multiplied by the native units. Are you sure native units is RPM and not edges per second? In the case of a Talon SRX, the native velocity unit is edges per .01 seconds, for example.

It looks like you are scaling the velocity twice. The encoder conversion factor is being set, but the encoder’s returned velocity is also being divided by 60 in your suppliers on lines 90 and 95.

I see you have a comment on your getHeading() method. Make sure you are inverting the heading per the note in the WPILib docs:

As your robot turns to the left, your gyroscope angle should increase.

Talon encoder native units are ticks/edges. Neos encoders return revolutions and RPM. Big difference.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.