Skip to content

Commit

Permalink
add DriveToPoseSimple and MinTimeDriveController
Browse files Browse the repository at this point in the history
  • Loading branch information
truher committed Oct 31, 2024
1 parent 8d37091 commit 88c8321
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BooleanSupplier;

import org.team100.frc2024.commands.AutonCommand;
Expand Down Expand Up @@ -33,14 +32,11 @@
import org.team100.lib.async.Async;
import org.team100.lib.async.AsyncFactory;
import org.team100.lib.commands.AllianceCommand;
import org.team100.lib.commands.drivetrain.DriveWithProfile2;
import org.team100.lib.commands.drivetrain.DriveToPoseSimple;
import org.team100.lib.commands.drivetrain.DriveWithProfileRotation;
import org.team100.lib.commands.drivetrain.FancyTrajectory;
import org.team100.lib.commands.drivetrain.ResetPose;
import org.team100.lib.commands.drivetrain.SetRotation;
import org.team100.lib.commands.drivetrain.for_testing.DriveInACircle;
import org.team100.lib.commands.drivetrain.for_testing.OscillateDirect;
import org.team100.lib.commands.drivetrain.for_testing.OscillateForceField;
import org.team100.lib.commands.drivetrain.for_testing.OscillateProfile;
import org.team100.lib.commands.drivetrain.manual.DriveManually;
import org.team100.lib.commands.drivetrain.manual.FieldManualWithNoteRotation;
Expand All @@ -56,6 +52,7 @@
import org.team100.lib.controller.drivetrain.FullStateDriveController;
import org.team100.lib.controller.drivetrain.HolonomicDriveControllerFactory;
import org.team100.lib.controller.drivetrain.HolonomicFieldRelativeController;
import org.team100.lib.controller.drivetrain.MinTimeDriveController;
import org.team100.lib.dashboard.Glassy;
import org.team100.lib.follower.DrivePIDFFollower;
import org.team100.lib.follower.DriveTrajectoryFollower;
Expand Down Expand Up @@ -95,9 +92,7 @@
import edu.wpi.first.math.controller.PIDController;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj2.command.Command;
Expand Down Expand Up @@ -273,8 +268,16 @@ public RobotContainer(TimedRobot100 robot) throws IOException {
m_drive,
halfFullStateController,
swerveKinodynamics), intake.run(intake::intakeSmart)));
// try the new mintime controller
final HolonomicFieldRelativeController minTimeController = new MinTimeDriveController(comLog, hlog);
whileTrue(driverControl::actualCircle,
new DriveInACircle(comLog, m_drive, controller, -1, viz));
new DriveToPoseSimple(
comLog,
new Pose2d(8, 4, GeometryUtil.kRotationZero),
minTimeController,
m_drive));
// whileTrue(driverControl::actualCircle,
// new DriveInACircle(comLog, m_drive, controller, -1, viz));

whileTrue(driverControl::driveToAmp,
new DriveToAmp(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.team100.lib.commands.drivetrain;

import org.team100.lib.controller.drivetrain.HolonomicFieldRelativeController;
import org.team100.lib.dashboard.Glassy;
import org.team100.lib.logging.Level;
import org.team100.lib.logging.LoggerFactory;
import org.team100.lib.logging.LoggerFactory.FieldRelativeVelocityLogger;
import org.team100.lib.motion.drivetrain.SwerveDriveSubsystem;
import org.team100.lib.motion.drivetrain.SwerveState;
import org.team100.lib.motion.drivetrain.kinodynamics.FieldRelativeVelocity;

import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.wpilibj2.command.Command;

/**
* Given a pose, drive there using only the holonomic controller,
* no profile, no trajectory. This doesn't seem to work well to follow
* a trajectory or profile, don't use it for that, just give it a point
* you want to go to, and it will go there. It makes no attempt to
* impose feasibility constraints or coordinate the axes.
*/
public class DriveToPoseSimple extends Command implements Glassy {
private final FieldRelativeVelocityLogger m_log_output;
private final SwerveState m_goal;
private final HolonomicFieldRelativeController m_controller;
private final SwerveDriveSubsystem m_swerve;

public DriveToPoseSimple(
LoggerFactory parent,
Pose2d goal,
HolonomicFieldRelativeController controller,
SwerveDriveSubsystem swerve) {
LoggerFactory child = parent.child(this);
m_log_output = child.fieldRelativeVelocityLogger(Level.TRACE, "output");
// goal is motionless at the specified pose.
m_goal = new SwerveState(goal);
m_controller = controller;
m_swerve = swerve;
}

@Override
public void initialize() {
// ?
}

@Override
public void execute() {
SwerveState measurement = m_swerve.getState();
FieldRelativeVelocity output = m_controller.calculate(measurement, m_goal);
m_log_output.log(() -> output);
m_swerve.driveInFieldCoordsVerbatim(output);
}

@Override
public boolean isFinished() {
return m_controller.atReference();
}

@Override
public void end(boolean interrupted) {
m_swerve.stop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.team100.lib.controller.drivetrain;

import org.team100.lib.controller.simple.MinTimeController;
import org.team100.lib.framework.TimedRobot100;
import org.team100.lib.logging.LoggerFactory;
import org.team100.lib.motion.drivetrain.SwerveState;
import org.team100.lib.motion.drivetrain.kinodynamics.FieldRelativeVelocity;
import org.team100.lib.state.State100;

import edu.wpi.first.math.MathUtil;

/** Min-time controllers on all axes. */
public class MinTimeDriveController implements HolonomicFieldRelativeController {
/** Should be stronger than switching. */
private static final int INITIAL_CURVE_ACCEL = 12;
/** Should be weaker than switching. */
private static final int GOAL_CURVE_ACCEL = 7;
private static final int SWITCHING_CURVE_ACCEL = 9;
private static final double[] FULL_STATE_K = new double[] { 2.0, 0.2 };
/** Switch to full-state proportional when this close to the goal. */
private static final double EASE = 0.1;
private static final double TOLERANCE = 0.01;
private static final int MAX_OMEGA_RAD_S = 5;
private static final int MAX_VELOCITY_M_S = 5;
private final MinTimeController m_xController;
private final MinTimeController m_yController;
private final MinTimeController m_thetaController;
private final Log m_log;

public MinTimeDriveController(LoggerFactory parent, Log log) {
LoggerFactory child = parent.child(this);
m_xController = new MinTimeController(
child,
x -> x,
MAX_VELOCITY_M_S,
SWITCHING_CURVE_ACCEL,
GOAL_CURVE_ACCEL,
INITIAL_CURVE_ACCEL,
TOLERANCE,
EASE,
FULL_STATE_K);
m_yController = new MinTimeController(
child,
x -> x,
MAX_VELOCITY_M_S,
SWITCHING_CURVE_ACCEL,
GOAL_CURVE_ACCEL,
INITIAL_CURVE_ACCEL,
TOLERANCE,
EASE,
FULL_STATE_K);
m_thetaController = new MinTimeController(
child,
MathUtil::angleModulus,
MAX_OMEGA_RAD_S,
SWITCHING_CURVE_ACCEL,
GOAL_CURVE_ACCEL,
INITIAL_CURVE_ACCEL,
TOLERANCE,
EASE,
FULL_STATE_K);
m_log = log;
}

@Override
public boolean atReference() {
return m_xController.atSetpoint() &&
m_yController.atSetpoint() &&
m_thetaController.atSetpoint();
}

/**
* Makes no attempt to coordinate the axes or provide feasible output.
*/
@Override
public FieldRelativeVelocity calculate(SwerveState measurement, SwerveState reference) {
m_log.measurement.log(() -> measurement);
m_log.reference.log(() -> reference);
m_log.error.log(() -> reference.minus(measurement));

FieldRelativeVelocity u_FF = reference.velocity();

State100 xFB = m_xController.calculate(
TimedRobot100.LOOP_PERIOD_S,
measurement.x(),
reference.x());
State100 yFB = m_yController.calculate(
TimedRobot100.LOOP_PERIOD_S,
measurement.y(),
reference.y());
State100 thetaFB = m_thetaController.calculate(
TimedRobot100.LOOP_PERIOD_S,
measurement.theta(),
reference.theta());

FieldRelativeVelocity u_FB = new FieldRelativeVelocity(
xFB.v(), yFB.v(), thetaFB.v());
m_log.u_FB.log(() -> u_FB);
return u_FF.plus(u_FB);
}

@Override
public void reset() {
// nothing to do
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.team100.lib.controller.simple;

import java.util.function.DoubleUnaryOperator;

import org.team100.lib.logging.Level;
import org.team100.lib.logging.LoggerFactory;
import org.team100.lib.logging.LoggerFactory.DoubleLogger;
import org.team100.lib.logging.LoggerFactory.State100Logger;
import org.team100.lib.state.State100;

/**
* Patterned after FullStateDriveController.
*/
public class FullStateController {

private final State100Logger m_log_measurement;
private final State100Logger m_log_reference; // ref v is FF
private final State100Logger m_log_error;
private final DoubleLogger m_log_u_FB;
private final double m_K1; // position
private final double m_K2; // velocity
private final DoubleUnaryOperator m_modulus;
private final double m_tol1;
private final double m_tol2;

private boolean m_atSetpoint = false;

public FullStateController(
LoggerFactory parent,
double k1,
double k2,
DoubleUnaryOperator modulus,
double xtol,
double vtol) {
LoggerFactory child = parent.child("FullStateController");
m_log_reference = child.state100Logger(Level.DEBUG, "reference");
m_log_measurement = child.state100Logger(Level.DEBUG, "measurement");
m_log_error = child.state100Logger(Level.DEBUG, "error");
m_log_u_FB = child.doubleLogger(Level.DEBUG, "u_FB");
m_K1 = k1;
m_K2 = k2;
m_modulus = modulus;
m_tol1 = xtol;
m_tol2 = vtol;
}

public double calculate(State100 measurement, State100 reference) {
m_log_measurement.log(() -> measurement);
m_log_reference.log(() -> reference);
m_log_error.log(() -> reference.minus(measurement));
double u_FF = reference.v();
m_atSetpoint = true;
double u_FB = calculateFB(measurement, reference);
m_log_u_FB.log(() -> u_FB);
return u_FF + u_FB;
}

private double calculateFB(State100 measurement, State100 setpoint) {
double xError = m_modulus.applyAsDouble(setpoint.x() - measurement.x());
double xDotError = setpoint.v() - measurement.v();
m_atSetpoint &= Math.abs(xError) < m_tol1 && Math.abs(xDotError) < m_tol2;
return m_K1 * xError + m_K2 * xDotError;
}

/** True if the most recent call to calculate() was at the setpoint. */
public boolean atSetpoint() {
return m_atSetpoint;
}

public void reset() {
m_atSetpoint = false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public class MinTimeController implements Glassy {

private final StringLogger m_log_mode;

private boolean m_atSetpoint;

/**
* @param modulus for angle wrapping
* @param maxVel
Expand Down Expand Up @@ -185,6 +187,8 @@ private State100 modulus(State100 s) {
* period.
*/
public State100 calculate(double dt, final State100 initialRaw, final State100 goalRaw) {
m_log_mode.log(() -> "calculating");

State100 initial = new State100(initialRaw.x(),
MathUtil.clamp(initialRaw.v(), -m_maxVelocity, m_maxVelocity));

Expand All @@ -194,8 +198,10 @@ public State100 calculate(double dt, final State100 initialRaw, final State100 g
m_modulus.applyAsDouble(goalRaw.x() - initialRaw.x()) + initialRaw.x(),
MathUtil.clamp(goalRaw.v(), -m_maxVelocity, m_maxVelocity));

m_atSetpoint = false;
// AT THE GOAL: DO NOTHING
if (goal.near(initial, m_tolerance)) {
m_atSetpoint = true;
m_log_mode.log(() -> "within tolerance");
return modulus(goal);
}
Expand Down Expand Up @@ -274,6 +280,11 @@ public State100 calculate(double dt, final State100 initialRaw, final State100 g
return fullG(dt, initial, -1);
}

/** True if the most-recent call to calculate is within tolerance. */
public boolean atSetpoint() {
return m_atSetpoint;
}

/**
* On the I path, what should we do?
*
Expand Down Expand Up @@ -393,6 +404,7 @@ private double truncateDt(double dt, State100 in_initial, State100 in_goal) {
* For I paths, use slightly-stronger effort.
*/
private State100 fullI(double dt, State100 in_initial, double direction) {
m_log_mode.log(() -> "full I");
// final double scale = 0.1;
direction = MathUtil.clamp(direction, -1, 1);
double x_i = in_initial.x();
Expand All @@ -407,6 +419,7 @@ private State100 fullI(double dt, State100 in_initial, double direction) {
* For G paths, use slightly-weaker effort.
*/
private State100 fullG(double dt, State100 in_initial, double direction) {
m_log_mode.log(() -> "full I");
// final double scale = 0.1;
direction = MathUtil.clamp(direction, -1, 1);
double x_i = in_initial.x();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ public void driveInFieldCoords(FieldRelativeVelocity vIn) {
m_swerveLocal.setChassisSpeeds(targetChassisSpeeds, m_gyro.getYawRateNWU());
}

/** Skip all scaling, setpoint generator, etc. */
public void driveInFieldCoordsVerbatim(FieldRelativeVelocity vIn) {
ChassisSpeeds targetChassisSpeeds = ChassisSpeeds.fromFieldRelativeSpeeds(
vIn.x(),
vIn.y(),
vIn.theta(),
getState().pose().getRotation());
m_swerveLocal.setChassisSpeedsNormally(targetChassisSpeeds, m_gyro.getYawRateNWU());
}

/**
* steer the wheels to match the target but don't drive them. This is for the
* beginning of trajectories, like the "square" project or any other case where
Expand Down
Loading

0 comments on commit 88c8321

Please sign in to comment.