[wpilib] Clean up physics simulation class APIs (#2763)

This commit is contained in:
Prateek Machiraju
2020-10-16 00:00:45 -04:00
committed by GitHub
parent 8f3e5794b3
commit 061432147d
19 changed files with 746 additions and 483 deletions

View File

@@ -16,106 +16,186 @@ import edu.wpi.first.wpiutil.math.VecBuilder;
import edu.wpi.first.wpiutil.math.numbers.N1;
import edu.wpi.first.wpiutil.math.numbers.N2;
/**
* Represents a simulated elevator mechanism.
*/
public class ElevatorSim extends LinearSystemSim<N2, N1, N1> {
// Gearbox for the elevator.
private final DCMotor m_gearbox;
private final DCMotor m_motor;
private final double m_drumRadiusMeters;
private final double m_minHeightMeters;
private final double m_maxHeightMeters;
// Gearing between the motors and the output.
private final double m_gearing;
// The radius of the drum that the elevator spool is wrapped around.
private final double m_drumRadius;
// The min allowable height for the elevator.
private final double m_minHeight;
// The max allowable height for the elevator.
private final double m_maxHeight;
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* * the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* * the simulation and write simulated outputs to sensors.
* Creates a simulated elevator mechanism.
*
* @param elevatorGearbox The {@link DCMotor} representing the elevator motor.
* @param carriageMassKg The mass of the carriage.
* @param drumRadiusMeters The radius of the drum driving the elevator.
* @param gearingReduction The reduction between motor and drum, as output over input.
* In most cases this is greater than one.
* @param m_measurementStdDevs Standard deviations for position noise. Can be null
* if no added noise is desired.
* @param plant The linear system that represents the elevator.
* @param gearbox The type of and number of motors in the elevator gearbox.
* @param gearing The gearing of the elevator (numbers greater than 1 represent reductions).
* @param drumRadiusMeters The radius of the drum that the elevator spool is wrapped around.
* @param minHeightMeters The min allowable height of the elevator.
* @param maxHeightMeters The max allowable height of the elevator.
*/
public ElevatorSim(DCMotor elevatorGearbox, double carriageMassKg,
double drumRadiusMeters, double gearingReduction,
double minHeightMeters, double maxHeightMeters,
Matrix<N1, N1> m_measurementStdDevs) {
super(LinearSystemId.createElevatorSystem(elevatorGearbox, carriageMassKg, drumRadiusMeters,
gearingReduction), m_measurementStdDevs);
this.m_motor = elevatorGearbox;
this.m_gearing = gearingReduction;
this.m_drumRadiusMeters = drumRadiusMeters;
this.m_minHeightMeters = minHeightMeters;
this.m_maxHeightMeters = maxHeightMeters;
public ElevatorSim(LinearSystem<N2, N1, N1> plant, DCMotor gearbox, double gearing,
double drumRadiusMeters, double minHeightMeters, double maxHeightMeters) {
this(plant, gearbox, gearing, drumRadiusMeters, minHeightMeters, maxHeightMeters, null);
}
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated elevator mechanism.
*
* @param elevatorPlant The elevator system being controlled. This system can be created
* with {@link edu.wpi.first.wpilibj.system.plant.LinearSystemId#createElevatorSystem(DCMotor, double, double, double)}
* or {@link edu.wpi.first.wpilibj.system.plant.LinearSystemId#identifyPositionSystem(double, double)}.
* @param m_measurementStdDevs Standard deviations of measurements. Can be null if addNoise is false.
* @param plant The linear system that represents the elevator.
* @param gearbox The type of and number of motors in the elevator gearbox.
* @param gearing The gearing of the elevator (numbers greater than 1 represent reductions).
* @param drumRadiusMeters The radius of the drum that the elevator spool is wrapped around.
* @param minHeightMeters The min allowable height of the elevator.
* @param maxHeightMeters The max allowable height of the elevator.
* @param measurementStdDevs The standard deviations of the measurements.
*/
public ElevatorSim(LinearSystem<N2, N1, N1> elevatorPlant,
Matrix<N1, N1> m_measurementStdDevs, DCMotor elevatorGearbox,
double gearingReduction, double drumRadiusMeters,
double minHeightMeters, double maxHeightMeters) {
super(elevatorPlant, m_measurementStdDevs);
this.m_motor = elevatorGearbox;
this.m_gearing = gearingReduction;
this.m_drumRadiusMeters = drumRadiusMeters;
this.m_minHeightMeters = minHeightMeters;
this.m_maxHeightMeters = maxHeightMeters;
public ElevatorSim(LinearSystem<N2, N1, N1> plant, DCMotor gearbox, double gearing,
double drumRadiusMeters, double minHeightMeters, double maxHeightMeters,
Matrix<N1, N1> measurementStdDevs) {
super(plant, measurementStdDevs);
m_gearbox = gearbox;
m_gearing = gearing;
m_drumRadius = drumRadiusMeters;
m_minHeight = minHeightMeters;
m_maxHeight = maxHeightMeters;
}
/**
* Creates a simulated elevator mechanism.
*
* @param gearbox The type of and number of motors in the elevator gearbox.
* @param gearing The gearing of the elevator (numbers greater than 1 represent reductions).
* @param carriageMassKg The mass of the elevator carriage.
* @param drumRadiusMeters The radius of the drum that the elevator spool is wrapped around.
* @param minHeightMeters The min allowable height of the elevator.
* @param maxHeightMeters The max allowable height of the elevator.
*/
public ElevatorSim(DCMotor gearbox, double gearing, double carriageMassKg,
double drumRadiusMeters, double minHeightMeters, double maxHeightMeters) {
this(gearbox, gearing, carriageMassKg, drumRadiusMeters, minHeightMeters, maxHeightMeters,
null);
}
/**
* Creates a simulated elevator mechanism.
*
* @param gearbox The type of and number of motors in the elevator gearbox.
* @param gearing The gearing of the elevator (numbers greater than 1 represent reductions).
* @param carriageMassKg The mass of the elevator carriage.
* @param drumRadiusMeters The radius of the drum that the elevator spool is wrapped around.
* @param minHeightMeters The min allowable height of the elevator.
* @param maxHeightMeters The max allowable height of the elevator.
* @param measurementStdDevs The standard deviations of the measurements.
*/
public ElevatorSim(DCMotor gearbox, double gearing, double carriageMassKg,
double drumRadiusMeters, double minHeightMeters, double maxHeightMeters,
Matrix<N1, N1> measurementStdDevs) {
super(LinearSystemId.createElevatorSystem(gearbox, carriageMassKg, drumRadiusMeters, gearing),
measurementStdDevs);
m_gearbox = gearbox;
m_gearing = gearing;
m_drumRadius = drumRadiusMeters;
m_minHeight = minHeightMeters;
m_maxHeight = maxHeightMeters;
}
/**
* Returns whether the elevator has hit the lower limit.
*
* @param x The current elevator state.
* @return Whether the elevator has hit the lower limit.
*/
public boolean hasHitLowerLimit(Matrix<N2, N1> x) {
return x.get(0, 0) < this.m_minHeightMeters;
return x.get(0, 0) < this.m_minHeight;
}
/**
* Returns whether the elevator has hit the upper limit.
*
* @param x The current elevator state.
* @return Whether the elevator has hit the upper limit.
*/
public boolean hasHitUpperLimit(Matrix<N2, N1> x) {
return x.get(0, 0) > this.m_maxHeightMeters;
}
@Override
protected Matrix<N2, N1> updateX(Matrix<N2, N1> currentXhat, Matrix<N1, N1> u, double dtSeconds) {
var updatedXhat = RungeKutta.rungeKutta(
(x, u_) -> (m_plant.getA().times(x)).plus(m_plant.getB().times(u_)).plus(VecBuilder.fill(0, -9.8)),
currentXhat, u, dtSeconds);
// We check for collision after updating xhat
if (hasHitLowerLimit(updatedXhat)) {
return VecBuilder.fill(m_minHeightMeters, 0);
} else if (hasHitUpperLimit(updatedXhat)) {
return VecBuilder.fill(m_maxHeightMeters, 0);
}
return updatedXhat;
}
public void setInputVoltage(double volts) {
setInput(volts);
return x.get(0, 0) > this.m_maxHeight;
}
/**
* Returns the position of the elevator.
*
* @return The position of the elevator.
*/
public double getPositionMeters() {
return getOutput(0);
}
/**
* Returns the velocity of the elevator.
*
* @return The velocity of the elevator.
*/
public double getVelocityMetersPerSecond() {
return m_x.get(0, 1);
}
/**
* Returns the elevator current draw.
*
* @return The elevator current draw.
*/
@Override
public double getCurrentDrawAmps() {
// I = V / R - omega / (Kv * R)
// Reductions are greater than 1, so a reduction of 10:1 would mean the motor is
// spinning 10x faster than the output
// v = r w, so w = v/r
double motorVelocityRadPerSec = getVelocityMetersPerSecond() / m_drumRadiusMeters * m_gearing;
var appliedVoltage = getInput(0);
return m_motor.getCurrent(motorVelocityRadPerSec, appliedVoltage) * Math.signum(appliedVoltage);
double motorVelocityRadPerSec = getVelocityMetersPerSecond() / m_drumRadius * m_gearing;
var appliedVoltage = m_u.get(0, 0);
return m_gearbox.getCurrent(motorVelocityRadPerSec, appliedVoltage) * Math.signum(appliedVoltage);
}
/**
* Sets the input voltage for the elevator.
*
* @param volts The input voltage.
*/
public void setInputVoltage(double volts) {
setInput(volts);
}
/**
* Updates the state of the elevator.
*
* @param currentXhat The current state estimate.
* @param u The system inputs (voltage).
* @param dtSeconds The time difference between controller updates.
*/
@Override
protected Matrix<N2, N1> updateX(Matrix<N2, N1> currentXhat,
Matrix<N1, N1> u, double dtSeconds) {
// Calculate updated x-hat from Runge-Kutta.
var updatedXhat = RungeKutta.rungeKutta(
(x, u_) -> (m_plant.getA().times(x)).plus(m_plant.getB().times(u_))
.plus(VecBuilder.fill(0, -9.8)), currentXhat, u, dtSeconds);
// We check for collisions after updating x-hat.
if (hasHitLowerLimit(updatedXhat)) {
return VecBuilder.fill(m_minHeight, 0);
}
if (hasHitUpperLimit(updatedXhat)) {
return VecBuilder.fill(m_maxHeight, 0);
}
return updatedXhat;
}
}

View File

@@ -14,66 +14,121 @@ import edu.wpi.first.wpilibj.util.Units;
import edu.wpi.first.wpiutil.math.Matrix;
import edu.wpi.first.wpiutil.math.numbers.N1;
/**
* Represents a simulated flywheel mechanism.
*/
public class FlywheelSim extends LinearSystemSim<N1, N1, N1> {
// Gearbox for the flywheel.
private final DCMotor m_gearbox;
private final DCMotor m_motor;
// The gearing from the motors to the output.
private final double m_gearing;
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated flywheel mechanism.
*
* @param flywheelPlant The flywheel system being controlled. This system can be created
* using {@link LinearSystemId#createFlywheelSystem(DCMotor, double, double)}
* or {@link LinearSystemId#identifyVelocitySystem(double, double)}
* @param flywheelGearbox The DCMotor representing the motor driving the flywheel.
* @param gearing The reduction between motor and drum, as output over input.
* In most cases this is greater than one.
* @param measurementStdDevs Standard deviations of measurements. Can be null if addNoise is false.
* @param plant The linear system that represents the flywheel.
* @param gearbox The type of and number of motors in the flywheel gearbox.
* @param gearing The gearing of the flywheel (numbers greater than 1
* represent reductions).
*/
public FlywheelSim(LinearSystem<N1, N1, N1> flywheelPlant, DCMotor flywheelGearbox,
double gearing, Matrix<N1, N1> measurementStdDevs) {
super(flywheelPlant, measurementStdDevs);
this.m_motor = flywheelGearbox;
this.m_gearing = gearing;
public FlywheelSim(LinearSystem<N1, N1, N1> plant, DCMotor gearbox, double gearing) {
super(plant);
m_gearbox = gearbox;
m_gearing = gearing;
}
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated flywheel mechanism.
*
* @param flywheelGearbox The DCMotor representing the motor driving the flywheel.
* @param gearing The reduction between motor and drum, as output over input.
* In most cases this is greater than one.
* @param jKgMetersSquared The moment of inertia J of the flywheel. This can be
* calculated with CAD. If J is unknown, {@link #FlywheelSim(LinearSystem, DCMotor, double, Matrix)}
* using FRC-Characterization's kV and kA may be better.
* @param measurementStdDevs Standard deviations of measurements. Can be null if addNoise is false.
* @param plant The linear system that represents the flywheel.
* @param gearbox The type of and number of motors in the flywheel
* gearbox.
* @param gearing The gearing of the flywheel (numbers greater
* than 1 represent reductions).
* @param measurementStdDevs The standard deviations of the measurements.
*/
public FlywheelSim(DCMotor flywheelGearbox, double gearing,
double jKgMetersSquared, Matrix<N1, N1> measurementStdDevs) {
super(LinearSystemId.createFlywheelSystem(flywheelGearbox, jKgMetersSquared, gearing),
measurementStdDevs);
this.m_motor = flywheelGearbox;
this.m_gearing = gearing;
public FlywheelSim(LinearSystem<N1, N1, N1> plant, DCMotor gearbox, double gearing,
Matrix<N1, N1> measurementStdDevs) {
super(plant, measurementStdDevs);
m_gearbox = gearbox;
m_gearing = gearing;
}
/**
* Creates a simulated flywheel mechanism.
*
* @param gearbox The type of and number of motors in the flywheel gearbox.
* @param gearing The gearing of the flywheel (numbers greater than 1
* represent reductions).
* @param jKgMetersSquared The moment of inertia of the flywheel. If this is unknown,
* use the {@link #FlywheelSim(LinearSystem, DCMotor, double, Matrix)}
* constructor.
*/
public FlywheelSim(DCMotor gearbox, double gearing, double jKgMetersSquared) {
super(LinearSystemId.createFlywheelSystem(gearbox, gearing, jKgMetersSquared));
m_gearbox = gearbox;
m_gearing = gearing;
}
/**
* Creates a simulated flywheel mechanism.
*
* @param gearbox The type of and number of motors in the flywheel
* gearbox.
* @param gearing The gearing of the flywheel (numbers greater
* than 1 represent reductions).
* @param jKgMetersSquared The moment of inertia of the flywheel. If this is unknown,
* use the {@link #FlywheelSim(LinearSystem, DCMotor, double, Matrix)}
* constructor.
* @param measurementStdDevs The standard deviations of the measurements.
*/
public FlywheelSim(DCMotor gearbox, double gearing, double jKgMetersSquared,
Matrix<N1, N1> measurementStdDevs) {
super(LinearSystemId.createFlywheelSystem(gearbox, gearing, jKgMetersSquared),
measurementStdDevs);
m_gearbox = gearbox;
m_gearing = gearing;
}
/**
* Returns the flywheel velocity.
*
* @return The flywheel velocity.
*/
public double getAngularVelocityRadPerSec() {
return getOutput(0);
}
/**
* Returns the flywheel velocity in RPM.
*
* @return The flywheel velocity in RPM.
*/
public double getAngularVelocityRPM() {
return Units.radiansPerSecondToRotationsPerMinute(getOutput(0));
}
/**
* Returns the flywheel current draw.
*
* @return The flywheel current draw.
*/
@Override
public double getCurrentDrawAmps() {
// I = V / R - omega / (Kv * R)
// Reductions are output over input, so a reduction of 2:1 means the motor is spinning
// 2x faster than the flywheel
return m_motor.getCurrent(getAngularVelocityRadPerSec() * m_gearing, m_u.get(0, 0))
return m_gearbox.getCurrent(getAngularVelocityRadPerSec() * m_gearing, m_u.get(0, 0))
* Math.signum(m_u.get(0, 0));
}
/**
* Sets the input voltage for the flywheel.
*
* @param volts The input voltage.
*/
public void setInputVoltage(double volts) {
setInput(volts);
}
}

View File

@@ -7,13 +7,14 @@
package edu.wpi.first.wpilibj.simulation;
import org.ejml.MatrixDimensionException;
import org.ejml.simple.SimpleMatrix;
import edu.wpi.first.wpilibj.math.StateSpaceUtil;
import edu.wpi.first.wpilibj.system.LinearSystem;
import edu.wpi.first.wpiutil.math.Matrix;
import edu.wpi.first.wpiutil.math.Num;
import edu.wpi.first.wpiutil.math.numbers.N1;
import org.ejml.MatrixDimensionException;
import org.ejml.simple.SimpleMatrix;
/**
* This class helps simulate linear systems. To use this class, do the following in the
@@ -23,35 +24,46 @@ import org.ejml.simple.SimpleMatrix;
*
* <p>Call {@link #update} to update the simulation.
*
* <p>Set simulated sensor readings with the simulated positions in {@link #getOutput(int)}
* <p>Set simulated sensor readings with the simulated positions in {@link #getOutput()}
*
* @param <States> The number of states of the system.
* @param <Inputs> The number of inputs to the system.
* @param <States> The number of states of the system.
* @param <Inputs> The number of inputs to the system.
* @param <Outputs> The number of outputs of the system.
*/
@SuppressWarnings("ClassTypeParameterName")
public class LinearSystemSim<States extends Num, Inputs extends Num,
Outputs extends Num> {
public class LinearSystemSim<States extends Num, Inputs extends Num, Outputs extends Num> {
// The plant that represents the linear system.
protected final LinearSystem<States, Inputs, Outputs> m_plant;
// Variables for state, output, and input.
@SuppressWarnings("MemberName")
protected Matrix<States, N1> m_x;
@SuppressWarnings("MemberName")
protected Matrix<Outputs, N1> m_y;
@SuppressWarnings("MemberName")
protected Matrix<Inputs, N1> m_u;
// The standard deviations of measurements, used for adding noise
// to the measurements.
protected final Matrix<Outputs, N1> m_measurementStdDevs;
/**
* Create a SimLinearSystem. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated generic linear system.
*
* @param system The system to simulate.
*/
public LinearSystemSim(LinearSystem<States, Inputs, Outputs> system) {
this(system, null);
}
/**
* Creates a simulated generic linear system with measurement noise.
*
* @param system The system being controlled.
* @param measurementStdDevs Standard deviations of measurements. Can be null if addNoise is false.
* @param measurementStdDevs Standard deviations of measurements.
*/
public LinearSystemSim(LinearSystem<States, Inputs, Outputs> system, Matrix<Outputs, N1> measurementStdDevs) {
public LinearSystemSim(LinearSystem<States, Inputs, Outputs> system,
Matrix<Outputs, N1> measurementStdDevs) {
this.m_plant = system;
this.m_measurementStdDevs = measurementStdDevs;
@@ -60,14 +72,10 @@ public class LinearSystemSim<States extends Num, Inputs extends Num,
m_y = new Matrix<>(new SimpleMatrix(system.getC().getNumRows(), 1));
}
protected Matrix<States, N1> updateX(Matrix<States, N1> currentXhat, Matrix<Inputs, N1> u, double dtSeconds) {
return m_plant.calculateX(currentXhat, u, dtSeconds);
}
/**
* Updates the simulation.
*
* @param dtSeconds The time that's passed since the last {@link #update(double)} call.
* @param dtSeconds The time between updates.
*/
@SuppressWarnings("LocalVariableName")
public void update(double dtSeconds) {
@@ -76,67 +84,93 @@ public class LinearSystemSim<States extends Num, Inputs extends Num,
// y = cx + du
m_y = m_plant.calculateY(m_x, m_u);
// Add measurement noise.
if (m_measurementStdDevs != null) {
m_y = m_y.plus(StateSpaceUtil.makeWhiteNoiseVector(m_measurementStdDevs));
}
}
public Matrix<Outputs, N1> getY() {
return m_y;
}
public double getY(int row) {
return m_y.get(row, 0);
}
public void setInput(Matrix<Inputs, N1> u) {
this.m_u = u;
}
public void setInput(int row, double value) {
m_u.set(row, 0, value);
}
public void setInput(double... u) {
if (u.length != m_u.getNumRows()) {
throw new MatrixDimensionException("Malformed input! Got " + u.length
+ " elements instead of " + m_u.getNumRows());
}
m_u = new Matrix<>(new SimpleMatrix(m_u.getNumRows(), 1, true, u));
}
public double getInput(int row) {
return m_u.get(row, 0);
}
public Matrix<Inputs, N1> getInput() {
return m_u;
}
/**
* Returns the current output of the plant.
*
* @return The current output of the plant.
*/
public Matrix<Outputs, N1> getOutput() {
return m_y;
}
/**
* Returns an element of the current output of the plant.
*
* @param row The row to return.
* @return An element of the current output of the plant.
*/
public double getOutput(int row) {
return m_y.get(row, 0);
}
/**
* Sets the system inputs (usually voltages).
*
* @param u The system inputs.
*/
public void setInput(Matrix<Inputs, N1> u) {
this.m_u = u;
}
/**
* Sets the system inputs.
*
* @param row The row in the input matrix to set.
* @param value The value to set the row to.
*/
public void setInput(int row, double value) {
m_u.set(row, 0, value);
}
/**
* Sets the system inputs.
*
* @param u An array of doubles that represent the inputs of the system.
*/
public void setInput(double... u) {
if (u.length != m_u.getNumRows()) {
throw new MatrixDimensionException("Malformed input! Got " + u.length
+ " elements instead of " + m_u.getNumRows());
}
m_u = new Matrix<>(new SimpleMatrix(m_u.getNumRows(), 1, true, u));
}
/**
* Sets the system state.
*
* @param state The state.
* @param state The new state.
*/
public void setState(Matrix<States, N1> state) {
m_x = state;
}
/**
* Get the current drawn by this simulated system. Override this method to add current
* calculation.
* Returns the current drawn by this simulated system. Override this method to add a custom
* current calculation.
*
* @return The currently drawn current.
* @return The current drawn by this simulated mechanism.
*/
public double getCurrentDrawAmps() {
return 0.0;
}
/**
* Updates the state estimate of the system.
*
* @param currentXhat The current state estimate.
* @param u The system inputs (usually voltage).
* @param dtSeconds The time difference between controller updates.
* @return The new state.
*/
protected Matrix<States, N1> updateX(Matrix<States, N1> currentXhat,
Matrix<Inputs, N1> u, double dtSeconds) {
return m_plant.calculateX(currentXhat, u, dtSeconds);
}
}

View File

@@ -16,195 +16,235 @@ import edu.wpi.first.wpiutil.math.VecBuilder;
import edu.wpi.first.wpiutil.math.numbers.N1;
import edu.wpi.first.wpiutil.math.numbers.N2;
/**
* Represents a simulated single jointed arm mechanism.
*/
public class SingleJointedArmSim extends LinearSystemSim<N2, N1, N1> {
// The gearbox for the arm.
private final DCMotor m_gearbox;
// The gearing between the motors and the output.
private final double m_gearing;
// The length of the arm.
@SuppressWarnings("MemberName")
private final double m_r;
private final double m_maxAngleRads;
private final double m_minAngleRads;
// The minimum angle that the arm is capable of.
private final double m_minAngle;
// The maximum angle that the arm is capable of.
private final double m_maxAngle;
// The mass of the arm.
private final double m_armMass;
private final DCMotor m_motor;
private final double m_gearing;
// Whether the simulator should simulate gravity.
private final boolean m_simulateGravity;
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated arm mechanism.
*
* @param armSystem The arm system being controlled.
* @param motor DCMotor representing the motor driving the arm.
* @param G The gear ratio of the arm (numbers greater than 1
* represent reductions).
* @param armMassKg The mass of the arm.
* @param armLengthMeters The distance from the pivot of the arm to its center of mass.
* This number is not the same as the overall length of the arm.
* @param minAngleRads The min angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param maxAngleRads The max angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param posMeasurementStdDev Standard deviations of noise in the arm's position measurement.
* @param simulateGravity If the affects of gravity should be simulated.
* @param plant The linear system that represents the arm.
* @param gearbox The type of and number of motors in the arm gearbox.
* @param gearing The gearing of the arm (numbers greater than 1 represent reductions).
* @param armLengthMeters The length of the arm.
* @param minAngleRads The minimum angle that the arm is capable of.
* @param maxAngleRads The maximum angle that the arm is capable of.
* @param armMassKg The mass of the arm.
* @param simulateGravity Whether gravity should be simulated or not.
*/
public SingleJointedArmSim(LinearSystem<N2, N1, N1> armSystem,
DCMotor motor, double G,
double armMassKg,
public SingleJointedArmSim(LinearSystem<N2, N1, N1> plant, DCMotor gearbox, double gearing,
double armLengthMeters, double minAngleRads, double maxAngleRads,
double posMeasurementStdDev, boolean simulateGravity) {
super(armSystem, VecBuilder.fill(posMeasurementStdDev));
this.m_armMass = armMassKg;
this.m_r = armLengthMeters;
this.m_minAngleRads = minAngleRads;
this.m_maxAngleRads = maxAngleRads;
this.m_motor = motor;
this.m_gearing = G;
this.m_simulateGravity = simulateGravity;
double armMassKg, boolean simulateGravity) {
this(plant, gearbox, gearing, armLengthMeters, minAngleRads, maxAngleRads, armMassKg,
simulateGravity, null);
}
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated arm mechanism.
*
* @param motor DCMotor representing the motor driving the arm.
* @param jKgSquaredMeters The moment of inertia of the arm.
* @param G The gear ratio of the arm (numbers greater than 1
* represent reductions).
* @param armMassKg The mass of the arm.
* @param armLengthMeters The distance from the pivot of the arm to its center of mass.
* @param minAngleRads The min angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param maxAngleRads The max angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param posMeasurementStdDev Standard deviations of noise in the arm's position measurement.
* @param simulateGravity If the affects of gravity should be simulated.
* @param plant The linear system that represents the arm.
* @param gearbox The type of and number of motors in the arm gearbox.
* @param gearing The gearing of the arm (numbers greater than 1 represent reductions).
* @param armLengthMeters The length of the arm.
* @param minAngleRads The minimum angle that the arm is capable of.
* @param maxAngleRads The maximum angle that the arm is capable of.
* @param armMassKg The mass of the arm.
* @param simulateGravity Whether gravity should be simulated or not.
* @param measurementStdDevs The standard deviations of the measurements.
*/
public SingleJointedArmSim(DCMotor motor, double jKgSquaredMeters,
double G, double armMassKg, double armLengthMeters,
double minAngleRads, double maxAngleRads,
double posMeasurementStdDev, boolean simulateGravity) {
this(LinearSystemId.createSingleJointedArmSystem(motor, jKgSquaredMeters, G),
motor, G,
armMassKg, armLengthMeters, minAngleRads, maxAngleRads,
posMeasurementStdDev, simulateGravity);
public SingleJointedArmSim(LinearSystem<N2, N1, N1> plant, DCMotor gearbox, double gearing,
double armLengthMeters, double minAngleRads, double maxAngleRads,
double armMassKg, boolean simulateGravity,
Matrix<N1, N1> measurementStdDevs) {
super(plant, measurementStdDevs);
m_gearbox = gearbox;
m_gearing = gearing;
m_r = armLengthMeters;
m_minAngle = minAngleRads;
m_maxAngle = maxAngleRads;
m_armMass = armMassKg;
m_simulateGravity = simulateGravity;
}
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated arm mechanism.
*
* @param motor DCMotor representing the motor driving the arm.
* @param G The gear ratio of the arm (numbers greater than 1
* represent reductions).
* @param armMassKg The mass of the arm.
* @param armLengthMeters The distance from the pivot of the arm to its center of mass.
* @param minAngleRads The min angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param maxAngleRads The max angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param positionMeasurementStdDevs Standard deviations of noise in the arm's position measurement.
* @param simulateGravity If the affects of gravity should be simulated.
* @param gearbox The type of and number of motors in the arm gearbox.
* @param gearing The gearing of the arm (numbers greater than 1 represent reductions).
* @param jKgMetersSquared The moment of inertia of the arm. This can be calculated from CAD software.
* @param armLengthMeters The length of the arm.
* @param minAngleRads The minimum angle that the arm is capable of.
* @param maxAngleRads The maximum angle that the arm is capable of.
* @param armMassKg The mass of the arm.
* @param simulateGravity Whether gravity should be simulated or not.
*/
public SingleJointedArmSim(DCMotor motor,
double G, double armMassKg, double armLengthMeters,
double minAngleRads, double maxAngleRads,
double positionMeasurementStdDevs, boolean simulateGravity) {
this(LinearSystemId.createSingleJointedArmSystem(motor,
1.0 / 3.0 * armMassKg * armLengthMeters * armLengthMeters, G),
motor, G,
armMassKg, armLengthMeters, minAngleRads, maxAngleRads,
positionMeasurementStdDevs, simulateGravity);
public SingleJointedArmSim(DCMotor gearbox, double gearing, double jKgMetersSquared,
double armLengthMeters, double minAngleRads, double maxAngleRads,
double armMassKg, boolean simulateGravity) {
this(gearbox, gearing, jKgMetersSquared, armLengthMeters, minAngleRads, maxAngleRads,
armMassKg, simulateGravity, null);
}
/**
* Create a LinearSystemSimulator. This simulator uses an {@link LinearSystem} to simulate
* the state of the system. In simulationPeriodic, users should first set inputs from motors, update
* the simulation and write simulated outputs to sensors.
* Creates a simulated arm mechanism.
*
* @param motor DCMotor representing the motor driving the arm.
* @param G The gear ratio of the arm (numbers greater than 1
* represent reductions).
* @param armMassKg The mass of the arm.
* @param armLengthMeters The distance from the pivot of the arm to its center of mass.
* @param minAngleRads The min angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param maxAngleRads The max angle the arm can reach, with 0 being measured from
* horizontal. The simulation will not allow motion past this.
* @param positionMeasurementStdDevs Standard deviations of noise in the arm's position measurement.
* @param gearbox The type of and number of motors in the arm gearbox.
* @param gearing The gearing of the arm (numbers greater than 1 represent reductions).
* @param jKgMetersSquared The moment of inertia of the arm. This can be calculated from CAD software.
* @param armLengthMeters The length of the arm.
* @param minAngleRads The minimum angle that the arm is capable of.
* @param maxAngleRads The maximum angle that the arm is capable of.
* @param armMassKg The mass of the arm.
* @param simulateGravity Whether gravity should be simulated or not.
* @param measurementStdDevs The standard deviations of the measurements.
*/
public SingleJointedArmSim(DCMotor motor,
double G, double armMassKg, double armLengthMeters,
double minAngleRads, double maxAngleRads,
double positionMeasurementStdDevs) {
this(LinearSystemId.createSingleJointedArmSystem(motor,
1.0 / 3.0 * armMassKg * armLengthMeters * armLengthMeters, G),
motor, G,
armMassKg, armLengthMeters, minAngleRads, maxAngleRads,
positionMeasurementStdDevs, true);
public SingleJointedArmSim(DCMotor gearbox, double gearing, double jKgMetersSquared,
double armLengthMeters, double minAngleRads, double maxAngleRads,
double armMassKg, boolean simulateGravity,
Matrix<N1, N1> measurementStdDevs) {
super(LinearSystemId.createSingleJointedArmSystem(gearbox, jKgMetersSquared, gearing),
measurementStdDevs);
m_gearbox = gearbox;
m_gearing = gearing;
m_r = armLengthMeters;
m_minAngle = minAngleRads;
m_maxAngle = maxAngleRads;
m_armMass = armMassKg;
m_simulateGravity = simulateGravity;
}
/**
* Returns whether the arm has hit the lower limit.
*
* @param x The current arm state.
* @return Whether the arm has hit the lower limit.
*/
public boolean hasHitLowerLimit(Matrix<N2, N1> x) {
return x.get(0, 0) < this.m_minAngleRads;
return x.get(0, 0) < this.m_minAngle;
}
/**
* Returns whether the arm has hit the upper limit.
*
* @param x The current arm state.
* @return Whether the arm has hit the upper limit.
*/
public boolean hasHitUpperLimit(Matrix<N2, N1> x) {
return x.get(0, 0) > this.m_maxAngleRads;
}
@Override
protected Matrix<N2, N1> updateX(Matrix<N2, N1> currentXhat, Matrix<N1, N1> u, double dtSeconds) {
/*
Horizontal case:
Torque = f * r = I * alpha
alpha = f * r / I
since f=mg,
alpha = m g r / I
Multiply RHS by cos(theta) to account for the arm angle
This acceleration is added to the linear system dynamics x-dot = Ax + Bu
We therefore find that f(x, u) = Ax + Bu + [[0] [m * g * r / I]]
*/
Matrix<N2, N1> updatedXhat = RungeKutta.rungeKutta(
this::dynamics,
currentXhat, u, dtSeconds);
// We check for collision after updating xhat
if (hasHitLowerLimit(updatedXhat)) {
return VecBuilder.fill(m_minAngleRads, 0);
} else if (hasHitUpperLimit(updatedXhat)) {
return VecBuilder.fill(m_maxAngleRads, 0);
}
return updatedXhat;
return x.get(0, 0) > this.m_maxAngle;
}
/**
* Returns the current arm angle.
*
* @return The current arm angle.
*/
public double getAngleRads() {
return m_x.get(0, 0);
}
/**
* Returns the current arm velocity.
*
* @return The current arm velocity.
*/
public double getVelocityRadPerSec() {
return m_x.get(1, 0);
}
/**
* Returns the arm current draw.
*
* @return The aram current draw.
*/
@Override
public double getCurrentDrawAmps() {
// Reductions are greater than 1, so a reduction of 10:1 would mean the motor is
// spinning 10x faster than the output
var motorVelocity = m_x.get(1, 0) * m_gearing;
return m_motor.getCurrent(motorVelocity, m_u.get(0, 0)) * Math.signum(m_u.get(0, 0));
return m_gearbox.getCurrent(motorVelocity, m_u.get(0, 0)) * Math.signum(m_u.get(0, 0));
}
private Matrix<N2, N1> dynamics(Matrix<N2, N1> x, Matrix<N1, N1> u_) {
Matrix<N2, N1> xdot = (m_plant.getA().times(x))
.plus(m_plant.getB().times(u_));
/**
* Sets the input voltage for the elevator.
*
* @param volts The input voltage.
*/
public void setInputVoltage(double volts) {
setInput(volts);
}
if (m_simulateGravity) {
xdot = xdot.plus(VecBuilder.fill(0,
m_armMass * m_r * -9.8 * 3.0 / (m_armMass * m_r * m_r) * Math.cos(x.get(0, 0))));
/**
* Calculates a rough estimate of the moment of inertia of an arm given its
* length and mass.
*
* @param lengthMeters The length of the arm.
* @param massKg The mass of the arm.
*
* @return The calculated moment of inertia.
*/
public static double estimateMOI(double lengthMeters, double massKg) {
return 1.0 / 3.0 * massKg * lengthMeters * lengthMeters;
}
/**
* Updates the state of the arm.
*
* @param currentXhat The current state estimate.
* @param u The system inputs (voltage).
* @param dtSeconds The time difference between controller updates.
*/
@Override
protected Matrix<N2, N1> updateX(Matrix<N2, N1> currentXhat,
Matrix<N1, N1> u, double dtSeconds) {
// Horizontal case:
// Torque = F * r = I * alpha
// alpha = F * r / I
// Since F = mg,
// alpha = m * g * r / I
// Finally, multiply RHS by cos(theta) to account for the arm angle
// This acceleration is added to the linear system dynamics x-dot = Ax + Bu
// We therefore find that f(x, u) = Ax + Bu + [[0] [m * g * r / I *
// cos(theta)]]
Matrix<N2, N1> updatedXhat = RungeKutta.rungeKutta(
(Matrix<N2, N1> x, Matrix<N1, N1> u_) -> {
Matrix<N2, N1> xdot = m_plant.getA().times(x).plus(m_plant.getB().times(u_));
if (m_simulateGravity) {
xdot = xdot.plus(VecBuilder.fill(0,
m_armMass * m_r * -9.8 * 3.0 / (m_armMass * m_r * m_r) * Math.cos(x.get(0, 0))));
}
return xdot;
},
currentXhat, u, dtSeconds);
// We check for collision after updating xhat
if (hasHitLowerLimit(updatedXhat)) {
return VecBuilder.fill(m_minAngle, 0);
}
return xdot;
if (hasHitUpperLimit(updatedXhat)) {
return VecBuilder.fill(m_maxAngle, 0);
}
return updatedXhat;
}
}