2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
package edu.wpi.first.wpilibj.simulation;
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
|
2021-05-01 15:53:30 +00:00
|
|
|
import edu.wpi.first.math.Matrix;
|
|
|
|
|
import edu.wpi.first.math.Nat;
|
|
|
|
|
import edu.wpi.first.math.VecBuilder;
|
|
|
|
|
import edu.wpi.first.math.Vector;
|
2024-04-29 22:01:42 -07:00
|
|
|
import edu.wpi.first.math.controller.LTVUnicycleController;
|
2021-05-01 15:53:30 +00:00
|
|
|
import edu.wpi.first.math.controller.LinearPlantInversionFeedforward;
|
|
|
|
|
import edu.wpi.first.math.geometry.Pose2d;
|
|
|
|
|
import edu.wpi.first.math.geometry.Rotation2d;
|
|
|
|
|
import edu.wpi.first.math.kinematics.DifferentialDriveKinematics;
|
|
|
|
|
import edu.wpi.first.math.numbers.N1;
|
|
|
|
|
import edu.wpi.first.math.numbers.N7;
|
|
|
|
|
import edu.wpi.first.math.system.NumericalIntegration;
|
|
|
|
|
import edu.wpi.first.math.system.plant.DCMotor;
|
|
|
|
|
import edu.wpi.first.math.system.plant.LinearSystemId;
|
|
|
|
|
import edu.wpi.first.math.trajectory.TrajectoryConfig;
|
|
|
|
|
import edu.wpi.first.math.trajectory.TrajectoryGenerator;
|
|
|
|
|
import edu.wpi.first.math.trajectory.constraint.DifferentialDriveKinematicsConstraint;
|
|
|
|
|
import edu.wpi.first.math.util.Units;
|
2020-09-20 09:39:52 -07:00
|
|
|
import java.util.List;
|
2020-12-29 22:45:16 -08:00
|
|
|
import org.junit.jupiter.api.Test;
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
class DifferentialDrivetrainSimTest {
|
|
|
|
|
@Test
|
2021-12-09 12:20:08 -08:00
|
|
|
void testConvergence() {
|
2020-09-20 09:39:52 -07:00
|
|
|
var motor = DCMotor.getNEO(2);
|
2020-12-29 22:45:16 -08:00
|
|
|
var plant =
|
|
|
|
|
LinearSystemId.createDrivetrainVelocitySystem(
|
|
|
|
|
motor, 50, Units.inchesToMeters(2), Units.inchesToMeters(12), 0.5, 1.0);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
var kinematics = new DifferentialDriveKinematics(Units.inchesToMeters(24));
|
2020-12-29 22:45:16 -08:00
|
|
|
var sim =
|
|
|
|
|
new DifferentialDrivetrainSim(
|
|
|
|
|
plant,
|
|
|
|
|
motor,
|
|
|
|
|
1,
|
2025-02-10 07:23:04 -08:00
|
|
|
kinematics.trackwidth,
|
2020-12-29 22:45:16 -08:00
|
|
|
Units.inchesToMeters(2),
|
|
|
|
|
VecBuilder.fill(0, 0, 0.0001, 0.1, 0.1, 0.005, 0.005));
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
var feedforward = new LinearPlantInversionFeedforward<>(plant, 0.020);
|
2024-04-29 22:01:42 -07:00
|
|
|
var feedback = new LTVUnicycleController(0.020);
|
2020-09-20 09:39:52 -07:00
|
|
|
feedforward.reset(VecBuilder.fill(0, 0));
|
|
|
|
|
|
|
|
|
|
// ground truth
|
|
|
|
|
Matrix<N7, N1> groundTruthX = new Vector<>(Nat.N7());
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
var traj =
|
|
|
|
|
TrajectoryGenerator.generateTrajectory(
|
2024-05-03 12:39:35 -07:00
|
|
|
Pose2d.kZero,
|
2020-12-29 22:45:16 -08:00
|
|
|
List.of(),
|
2024-05-03 12:39:35 -07:00
|
|
|
new Pose2d(2, 2, Rotation2d.kZero),
|
2020-12-29 22:45:16 -08:00
|
|
|
new TrajectoryConfig(1, 1)
|
|
|
|
|
.addConstraint(new DifferentialDriveKinematicsConstraint(kinematics, 1)));
|
2020-09-20 09:39:52 -07:00
|
|
|
|
2025-02-10 07:23:04 -08:00
|
|
|
for (double t = 0; t < traj.getTotalTime(); t += 0.020) {
|
2021-12-22 22:27:51 -08:00
|
|
|
var state = traj.sample(t);
|
2024-04-29 22:01:42 -07:00
|
|
|
var feedbackOut = feedback.calculate(sim.getPose(), state);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
2024-04-29 22:01:42 -07:00
|
|
|
var wheelSpeeds = kinematics.toWheelSpeeds(feedbackOut);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
2025-02-10 07:23:04 -08:00
|
|
|
var voltages = feedforward.calculate(VecBuilder.fill(wheelSpeeds.left, wheelSpeeds.right));
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
// Sim periodic code
|
|
|
|
|
sim.setInputs(voltages.get(0, 0), voltages.get(1, 0));
|
|
|
|
|
sim.update(0.020);
|
|
|
|
|
|
|
|
|
|
// Update our ground truth
|
2023-11-17 08:41:18 -08:00
|
|
|
groundTruthX = NumericalIntegration.rkdp(sim::getDynamics, groundTruthX, voltages, 0.020);
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
|
2021-09-16 23:42:46 -07:00
|
|
|
// 2 inch tolerance is OK since our ground truth is an approximation of the
|
2023-11-17 08:41:18 -08:00
|
|
|
// ODE solution using RKDP anyway
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
groundTruthX.get(DifferentialDrivetrainSim.State.kX.value, 0),
|
2021-09-16 23:42:46 -07:00
|
|
|
sim.getState(DifferentialDrivetrainSim.State.kX),
|
|
|
|
|
0.05);
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
groundTruthX.get(DifferentialDrivetrainSim.State.kY.value, 0),
|
2021-09-16 23:42:46 -07:00
|
|
|
sim.getState(DifferentialDrivetrainSim.State.kY),
|
|
|
|
|
0.05);
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
groundTruthX.get(DifferentialDrivetrainSim.State.kHeading.value, 0),
|
2021-09-16 23:42:46 -07:00
|
|
|
sim.getState(DifferentialDrivetrainSim.State.kHeading),
|
|
|
|
|
0.01);
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2021-12-09 12:20:08 -08:00
|
|
|
void testCurrent() {
|
2020-09-20 09:39:52 -07:00
|
|
|
var motor = DCMotor.getNEO(2);
|
2020-12-29 22:45:16 -08:00
|
|
|
var plant =
|
|
|
|
|
LinearSystemId.createDrivetrainVelocitySystem(
|
|
|
|
|
motor, 50, Units.inchesToMeters(2), Units.inchesToMeters(12), 0.5, 1.0);
|
2020-09-20 09:39:52 -07:00
|
|
|
var kinematics = new DifferentialDriveKinematics(Units.inchesToMeters(24));
|
2020-12-29 22:45:16 -08:00
|
|
|
var sim =
|
|
|
|
|
new DifferentialDrivetrainSim(
|
2025-02-10 07:23:04 -08:00
|
|
|
plant, motor, 1, kinematics.trackwidth, Units.inchesToMeters(2), null);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
sim.setInputs(-12, -12);
|
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
|
sim.update(0.020);
|
|
|
|
|
}
|
2025-02-10 07:23:04 -08:00
|
|
|
assertTrue(sim.getCurrentDraw() > 0);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
sim.setInputs(12, 12);
|
|
|
|
|
for (int i = 0; i < 20; i++) {
|
|
|
|
|
sim.update(0.020);
|
|
|
|
|
}
|
2025-02-10 07:23:04 -08:00
|
|
|
assertTrue(sim.getCurrentDraw() > 0);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
sim.setInputs(-12, 12);
|
|
|
|
|
for (int i = 0; i < 30; i++) {
|
|
|
|
|
sim.update(0.020);
|
|
|
|
|
}
|
2025-02-10 07:23:04 -08:00
|
|
|
assertTrue(sim.getCurrentDraw() > 0);
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2021-12-09 12:20:08 -08:00
|
|
|
void testModelStability() {
|
2020-09-20 09:39:52 -07:00
|
|
|
var motor = DCMotor.getNEO(2);
|
2020-12-29 22:45:16 -08:00
|
|
|
var plant =
|
|
|
|
|
LinearSystemId.createDrivetrainVelocitySystem(
|
|
|
|
|
motor, 50, Units.inchesToMeters(2), Units.inchesToMeters(12), 2.0, 5.0);
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
var kinematics = new DifferentialDriveKinematics(Units.inchesToMeters(24));
|
2020-12-29 22:45:16 -08:00
|
|
|
var sim =
|
|
|
|
|
new DifferentialDrivetrainSim(
|
|
|
|
|
plant,
|
|
|
|
|
motor,
|
|
|
|
|
5,
|
2025-02-10 07:23:04 -08:00
|
|
|
kinematics.trackwidth,
|
2020-12-29 22:45:16 -08:00
|
|
|
Units.inchesToMeters(2),
|
|
|
|
|
VecBuilder.fill(0, 0, 0.0001, 0.1, 0.1, 0.005, 0.005));
|
2020-09-20 09:39:52 -07:00
|
|
|
|
|
|
|
|
sim.setInputs(2, 4);
|
|
|
|
|
|
2020-09-27 09:31:29 -07:00
|
|
|
// 10 seconds should be enough time to verify stability
|
|
|
|
|
for (int i = 0; i < 500; i++) {
|
2020-09-20 09:39:52 -07:00
|
|
|
sim.update(0.020);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-27 16:25:17 -04:00
|
|
|
assertTrue(Math.abs(sim.getPose().getTranslation().getNorm()) < 100);
|
2020-09-20 09:39:52 -07:00
|
|
|
}
|
|
|
|
|
}
|