Skip to content

Commit

Permalink
Merge pull request #534 from truher/main
Browse files Browse the repository at this point in the history
min-time holonomic controller, also some estimator work
  • Loading branch information
truher authored Nov 1, 2024
2 parents 8cc79bd + 88c8321 commit 3c7a2bd
Show file tree
Hide file tree
Showing 34 changed files with 852 additions and 215 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 3c7a2bd

Please sign in to comment.