This is a guide on how to create a SubsystemIOSim.kt file for your subsystem. This file will communicate with requests from the logic file to command a physics simulation who’s physical outputs will be reported back to the IO.
<aside> 🚨
Example code is provided, but that does not guarentee it’s correct! Check the latest version of WPILib to verify code. If you see incorrect code, please update it!
Note that this is basic simulation, and is required for every subsystem. If you are just creating your basic simulation file, you’re in the right place! For more complex applications and additions to the simulation, such as game piece logic, redirect yourselves towards the MapleSim documentation. [LINK TO MAPLESIM DOC HERE]
</aside>
The simulation class will be declared as an object (singleton) instead of a class, as only 1 instance of the simulation will ever be made.
object SubsystemIOSim : SubsystemIO { /* ... */ }
Here is where it gets tricky. WPILib offers many different simulation classes for robotics physics simulation, but there’s only a select few you really need to worry about.
First, know about the
DCMotorclass.DCMotorallows you to create a motor with specified nominal voltage, stall current, etc. Retrieving the pre-configuredDCMotors can be done with thestatic DCMotor getMotorName(int numMotors)method, whereMotorNameis replaced by an actual motor’s name, andnumMotorsis the number of motors powering the mechanism of interest.
You can create an arm (pivot) simulation using the SingleJointedArmSim class. https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj/simulation/SingleJointedArmSim.html Note that this obviously represents an arm with a single joint (single point of turning). Here is an example constructor.
import edu.wpi.first.wpilibj.simulation.SingleJointedArmSim
import edu.wpi.first.math.system.plant.DCMotor
import com.team4099.robot2025.config.constants.SubsystemConstants
/* class fields */
private val subsystemSim = SingleJointedArmSim(
DCMotor.getMotorName(numMotors),
// reciprocal because in our code reductions are < 1, but in WPILib reductions are > 1
1.0 / SubsystemConstants.GEAR_RATIO,
// can be taken either from CAD or replaced with a call to SingleJointedArmSim.estimateMoi(double lengthMeters,double massKg)
SubsystemConstants.MOMENT_OF_INERTIA.inKilogramMeterSquared,
SubsystemConstants.ARM_LENGTH.inMeters,
SubsystemConstants.MIN_ANGLE.inRadians,
SubsystemConstants.MAX_ANGLE.inRadians,
// simulate gravity boolean. can set to false if want easy tuning, but should set to true for FF tuning
true,
// likely the MIN_ANGLE or MAX_ANGLE
SubsystemConstants.SIM_STARTING_ANGLE.inRadians
)
You can create an elevator (extension) simulation using the ElevatorSim class. https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj/simulation/ElevatorSim.html Note that this can be used for both vertical and horizontal elevators (laterators). Here is an example constructor.
import edu.wpi.first.wpilibj.simulation.ElevatorSim
import edu.wpi.first.math.system.plant.DCMotor
import com.team4099.robot2025.config.constants.SubsystemConstants
/* class fields */
private val subsystemSim = ElevatorSim(
DCMotor.getMotorName(numMotors),
// reciprocal because in our code reductions are < 1, but in WPILib reductions are > 1
1.0 / SubsystemConstants.GEAR_RATIO,
// should also include everything being lifted (mounted) on the carriage
SubsystemConstants.CARRIAGE_MASS.inKilograms,
SubsystemConstants.DRUM_RADIUS.inMeters,
SubsystemConstants.MIN_HEIGHT.inMeters,
SubsystemConstants.MAX_HEIGHT.inMeters,
// simulate gravity boolean. only true if vertical elevator. can set to false if want easy tuning, but should set to true for FF tuning
true,
// likely the MIN_HEIGHT or MAX_HEIGHT
SubsystemConstants.SIM_STARTING_HEIGHT.inMeters
)
You can create a flywheel (rollers) simulation using the FlywheelSim class. https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj/simulation/FlywheelSim.html Note that this can be used for open-loop rollers or a velocity-controlled flywheel. Here is an example constructor.
import edu.wpi.first.wpilibj.simulation.FlywheelSim
import edu.wpi.first.math.system.plant.DCMotor
import edu.wpi.first.math.system.plant.LinearSystemId
import com.team4099.robot2025.config.constants.SubsystemConstants
/* class fields */
private val subsystemSim = FlywheelSim(
LinearSystemId.createFlywheelSystem(
DCMotor.getMotorName(numMotors),
SubsystemConstants.MOMENT_OF_INERTIA.inKilogramMeterSquared,
// reciprocal because in our code reductions are < 1, but in WPILib reductions are > 1
1.0 / SubsystemConstants.GEAR_RATIO,
),
DCMotor.getMotorName(numMotors),
)