2026-02-20 18:30:35 -05: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.
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
import wpilib
|
|
|
|
|
import wpilib.simulation
|
|
|
|
|
import wpimath
|
|
|
|
|
|
|
|
|
|
import constants
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Elevator:
|
|
|
|
|
"""Represents the elevator subsystem."""
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
# This gearbox represents a gearbox containing 4 Vex 775pro motors.
|
|
|
|
|
self.elevatorGearbox = wpimath.DCMotor.vex775Pro(4)
|
|
|
|
|
|
|
|
|
|
# Standard classes for controlling our elevator
|
|
|
|
|
self.controller = wpimath.ProfiledPIDController(
|
|
|
|
|
constants.kElevatorKp,
|
|
|
|
|
constants.kElevatorKi,
|
|
|
|
|
constants.kElevatorKd,
|
|
|
|
|
wpimath.TrapezoidProfile.Constraints(2.45, 2.45),
|
|
|
|
|
)
|
|
|
|
|
self.feedforward = wpimath.ElevatorFeedforward(
|
|
|
|
|
constants.kElevatorkS,
|
|
|
|
|
constants.kElevatorkG,
|
|
|
|
|
constants.kElevatorkV,
|
|
|
|
|
constants.kElevatorkA,
|
|
|
|
|
)
|
|
|
|
|
self.encoder = wpilib.Encoder(
|
|
|
|
|
constants.kEncoderAChannel, constants.kEncoderBChannel
|
|
|
|
|
)
|
|
|
|
|
self.motor = wpilib.PWMSparkMax(constants.kMotorPort)
|
|
|
|
|
|
|
|
|
|
# Simulation classes help us simulate what's going on, including gravity.
|
|
|
|
|
self.elevatorSim = wpilib.simulation.ElevatorSim(
|
|
|
|
|
self.elevatorGearbox,
|
|
|
|
|
constants.kElevatorGearing,
|
|
|
|
|
constants.kCarriageMass,
|
|
|
|
|
constants.kElevatorDrumRadius,
|
|
|
|
|
constants.kMinElevatorHeight,
|
|
|
|
|
constants.kMaxElevatorHeight,
|
|
|
|
|
True,
|
|
|
|
|
0,
|
|
|
|
|
[0.01, 0.0],
|
|
|
|
|
)
|
|
|
|
|
self.encoderSim = wpilib.simulation.EncoderSim(self.encoder)
|
|
|
|
|
self.motorSim = wpilib.simulation.PWMMotorControllerSim(self.motor)
|
|
|
|
|
|
|
|
|
|
# Create a Mechanism2d visualization of the elevator
|
|
|
|
|
self.mech2d = wpilib.Mechanism2d(20, 50)
|
|
|
|
|
self.mech2dRoot = self.mech2d.getRoot("Elevator Root", 10, 0)
|
|
|
|
|
self.elevatorMech2d = self.mech2dRoot.appendLigament(
|
|
|
|
|
"Elevator", self.elevatorSim.getPosition(), 90
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.encoder.setDistancePerPulse(constants.kElevatorEncoderDistPerPulse)
|
|
|
|
|
|
|
|
|
|
# Publish Mechanism2d to SmartDashboard
|
|
|
|
|
# To view the Elevator visualization, select Network Tables -> SmartDashboard -> Elevator Sim
|
|
|
|
|
wpilib.SmartDashboard.putData("Elevator Sim", self.mech2d)
|
|
|
|
|
|
|
|
|
|
def simulationPeriodic(self) -> None:
|
|
|
|
|
# In this method, we update our simulation of what our elevator is doing
|
|
|
|
|
# First, we set our "inputs" (voltages)
|
|
|
|
|
self.elevatorSim.setInputVoltage(
|
2026-03-07 00:21:43 -05:00
|
|
|
self.motorSim.getDutyCycle() * wpilib.RobotController.getBatteryVoltage()
|
2026-02-20 18:30:35 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Next, we update it. The standard loop time is 20ms.
|
|
|
|
|
self.elevatorSim.update(0.020)
|
|
|
|
|
|
|
|
|
|
# Finally, we set our simulated encoder's readings and simulated battery voltage
|
|
|
|
|
self.encoderSim.setDistance(self.elevatorSim.getPosition())
|
|
|
|
|
# SimBattery estimates loaded battery voltages
|
|
|
|
|
wpilib.simulation.RoboRioSim.setVInVoltage(
|
|
|
|
|
wpilib.simulation.BatterySim.calculate([self.elevatorSim.getCurrentDraw()])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def reachGoal(self, goal: float) -> None:
|
|
|
|
|
"""Run control loop to reach and maintain goal.
|
|
|
|
|
|
|
|
|
|
:param goal: the position to maintain
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
self.controller.setGoal(goal)
|
|
|
|
|
|
|
|
|
|
# With the setpoint value we run PID control like normal
|
|
|
|
|
pidOutput = self.controller.calculate(self.encoder.getDistance())
|
|
|
|
|
feedforwardOutput = self.feedforward.calculate(
|
|
|
|
|
self.controller.getSetpoint().velocity
|
|
|
|
|
)
|
|
|
|
|
self.motor.setVoltage(pidOutput + feedforwardOutput)
|
|
|
|
|
|
|
|
|
|
def stop(self) -> None:
|
|
|
|
|
"""Stop the control loop and motor output."""
|
|
|
|
|
self.controller.setGoal(0.0)
|
2026-03-07 00:21:43 -05:00
|
|
|
self.motor.setDutyCycle(0.0)
|
2026-02-20 18:30:35 -05:00
|
|
|
|
|
|
|
|
def updateTelemetry(self) -> None:
|
|
|
|
|
"""Update telemetry, including the mechanism visualization."""
|
|
|
|
|
# Update elevator visualization with position
|
|
|
|
|
self.elevatorMech2d.setLength(self.encoder.getDistance())
|