001/** 002 * Copyright 2024 The Space Cookies : Girl Scout Troop #62868 and FRC Team #1868 003 * Open Source Software; you may modify and/or share it under the terms of 004 * the 3-Clause BSD License found in the root directory of this project. 005 */ 006 007package tagalong.subsystems.micro; 008 009import com.ctre.phoenix6.signals.InvertedValue; 010import edu.wpi.first.math.controller.ElevatorFeedforward; 011import edu.wpi.first.math.trajectory.TrapezoidProfile; 012import edu.wpi.first.wpilibj.RobotController; 013import edu.wpi.first.wpilibj.simulation.BatterySim; 014import edu.wpi.first.wpilibj.simulation.ElevatorSim; 015import edu.wpi.first.wpilibj.simulation.RoboRioSim; 016import edu.wpi.first.wpilibj.smartdashboard.Mechanism2d; 017import edu.wpi.first.wpilibj.smartdashboard.MechanismLigament2d; 018import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; 019import edu.wpi.first.wpilibj.util.Color; 020import edu.wpi.first.wpilibj.util.Color8Bit; 021import tagalong.TagalongConfiguration; 022import tagalong.math.AlgebraicUtils; 023import tagalong.measurements.Height; 024import tagalong.subsystems.micro.confs.ElevatorConf; 025 026/** 027 * Elevator Microsystem 028 */ 029public class Elevator extends Microsystem { 030 /** 031 * Configuration for the elevator 032 */ 033 public final ElevatorConf _elevatorConf; 034 035 /* -------- Hardware: motors and sensors -------- */ 036 037 /* -------- Control: states and constants -------- */ 038 /** 039 * Ratio between motor rotations and drum rotations 040 */ 041 protected final double _motorToMechRatio; 042 /** 043 * Minimum height of the elevator in meters, 044 * Maximum height of the elevator in meters 045 */ 046 public final double _elevatorMinHeightM, _elevatorMaxHeightM; 047 /** 048 * Maximum velocity of the elevator in meters per second, 049 * Maximum acceleration of the elevator in meters per second squared 050 */ 051 public final double _maxVelocityMPS, _maxAccelerationMPS2; 052 /** 053 * Default lower tolerance of the elevator in meters, 054 * Default upper tolerance of the elevator in meters 055 */ 056 public final double _defaultElevatorLowerToleranceM, _defaultElevatorUpperToleranceM; 057 /** 058 * Power for zeroing the elevator 059 */ 060 public final double _elevatorZeroingPower; 061 /** 062 * Positional tolerance in meters for zeroing the elevator 063 */ 064 public final double _elevatorZeroingStallToleranceM; 065 /** 066 * Duration of stalling in seconds necessary for the elevator zero command to finish 067 */ 068 public final double _elevatorZeroingDurationS; 069 070 /* -------- Control: controllers and utilities -------- */ 071 /** 072 * Feedforward model for the elevator 073 */ 074 protected ElevatorFeedforward _elevatorFF; 075 076 /* -------- Sim -------- */ 077 /** 078 * Simulation for the elevator 079 */ 080 protected ElevatorSim _elevatorSim; 081 /** 082 * Base stage ligament 083 */ 084 protected MechanismLigament2d _elevatorBaseStageLigament; 085 /** 086 * Stage 1 ligament 087 */ 088 protected MechanismLigament2d _elevatorStage1Ligament; 089 /** 090 * Velocity of the simulated elevator in meters per second 091 */ 092 protected double _simVelocityMPS; 093 /** 094 * Whether or not the primary motor is inverted 095 */ 096 protected boolean _primaryMotorInverted; 097 098 /** 099 * Constructs an elevator microsystem with the below configurations 100 * 101 * @param conf Configuration for the elevator 102 */ 103 public Elevator(ElevatorConf conf) { 104 super(conf); 105 _elevatorConf = conf; 106 107 if (_configuredMicrosystemDisable) { 108 _motorToMechRatio = 1.0; 109 _elevatorMinHeightM = 0.0; 110 _elevatorMaxHeightM = 0.0; 111 _maxVelocityMPS = 0.0; 112 _maxAccelerationMPS2 = 0.0; 113 _defaultElevatorLowerToleranceM = 0.0; 114 _defaultElevatorUpperToleranceM = 0.0; 115 _elevatorZeroingPower = 0.0; 116 _elevatorZeroingStallToleranceM = 0.0; 117 _elevatorZeroingDurationS = 0.0; 118 return; 119 } 120 121 _elevatorFF = _elevatorConf.feedForward; 122 _trapProfile = new TrapezoidProfile(_elevatorConf.trapezoidalLimits); 123 _curState.position = getElevatorHeightM(); 124 _motorToMechRatio = _elevatorConf.motorToMechRatio; 125 _elevatorMinHeightM = _elevatorConf.positionalMin; 126 _elevatorMaxHeightM = _elevatorConf.positionalMax; 127 _maxVelocityMPS = _elevatorConf.trapezoidalLimitsVelocity; 128 _maxAccelerationMPS2 = _elevatorConf.trapezoidalLimitsAcceleration; 129 _defaultElevatorLowerToleranceM = _elevatorConf.defaultLowerTolerance; 130 _defaultElevatorUpperToleranceM = _elevatorConf.defaultUpperTolerance; 131 _elevatorZeroingPower = -Math.abs(_elevatorConf.elevatorZeroingPower); 132 _elevatorZeroingStallToleranceM = _elevatorConf.elevatorZeroingStallToleranceM; 133 _elevatorZeroingDurationS = _elevatorConf.elevatorZeroingDurationS; 134 135 configAllDevices(); 136 } 137 138 // Override to ensure the position config happens after the devices are configured 139 @Override 140 public void configAllDevices() { 141 super.configAllDevices(); 142 143 // FUTURE DEV: Look into if all motors or just the leader need their positions set? 144 // for (var motor : _allMotors) motor.setPosition(0.0); 145 _primaryMotor.setPosition(0.0); 146 } 147 148 /** 149 * Calculates the next state according to the trapezoidal profile and requests the elevator 150 * motor(s) to arrive at the next position with feedforward 151 */ 152 public void followLastProfile() { 153 if (_isMicrosystemDisabled) { 154 return; 155 } 156 TrapezoidProfile.State nextState = 157 _trapProfile.calculate(TagalongConfiguration.LOOP_PERIOD_S, _curState, _goalState); 158 159 _primaryMotor.setControl(_requestedPositionVoltage 160 .withPosition(metersToMotor(nextState.position)) 161 // State is in meters, so the FF can handle the units directly 162 .withFeedForward(_elevatorFF.calculate(nextState.velocity))); 163 164 if (_isShuffleboardMicro) { 165 _targetPositionEntry.setDouble(nextState.position); 166 _targetVelocityEntry.setDouble(nextState.velocity); 167 } 168 169 _curState = nextState; 170 } 171 172 /** 173 * Creates a new trapezoidal profile for the elevator to follow 174 * 175 * @param goalPosition goal position in meters 176 */ 177 public void setElevatorProfile(Height goalPosition) { 178 setElevatorProfile(goalPosition.getHeightM()); 179 } 180 181 /** 182 * Creates a new trapezoidal profile for the elevator to follow 183 * 184 * @param goalPosition goal position in meters 185 * @param goalVelocityMPS goal velocity in meters per second 186 */ 187 public void setElevatorProfile(Height goalPosition, double goalVelocityMPS) { 188 setElevatorProfile(goalPosition.getHeightM(), goalVelocityMPS); 189 } 190 191 /** 192 * Creates a new trapezoidal profile for the elevator to follow 193 * 194 * @param goalPosition goal position in meters 195 * @param goalVelocityMPS goal velocity in meters per second 196 * @param maxVelocityMPS maximum velocity in meters per second 197 */ 198 public void setElevatorProfile( 199 Height goalPosition, double goalVelocityMPS, double maxVelocityMPS 200 ) { 201 setElevatorProfile(goalPosition.getHeightM(), goalVelocityMPS, maxVelocityMPS); 202 } 203 204 /** 205 * Creates a new trapezoidal profile for the elevator to follow 206 * 207 * @param goalPosition goal position in meters 208 * @param goalVelocityMPS goal velocity in meters per second 209 * @param maxVelocityMPS maximum velocity in meters per second 210 * @param maxAccelerationMPS2 maximum acceleration in meters per second squared 211 */ 212 public void setElevatorProfile( 213 Height goalPosition, double goalVelocityMPS, double maxVelocityMPS, double maxAccelerationMPS2 214 ) { 215 setElevatorProfile( 216 goalPosition.getHeightM(), goalVelocityMPS, maxVelocityMPS, maxAccelerationMPS2 217 ); 218 } 219 220 /** 221 * Creates a new trapezoidal profile for the elevator to follow 222 * 223 * @param goalPosition goal position in meters 224 * @param goalVelocityMPS goal velocity in meters per second 225 * @param maxVelocityMPS maximum velocity in meters per second 226 * @param maxAccelerationMPS2 maximum acceleration in meters per second squared 227 * @param setCurrentState True if the profiles current state should base itself off sensor 228 * values rather than continue from the existing state 229 */ 230 public void setElevatorProfile( 231 Height goalPosition, 232 double goalVelocityMPS, 233 double maxVelocityMPS, 234 double maxAccelerationMPS2, 235 boolean setCurrentState 236 ) { 237 setElevatorProfile( 238 goalPosition.getHeightM(), 239 goalVelocityMPS, 240 maxVelocityMPS, 241 maxAccelerationMPS2, 242 setCurrentState 243 ); 244 } 245 246 /** 247 * Creates a new trapezoidal profile for the elevator to follow 248 * 249 * @param goalPositionM goal position in meters 250 */ 251 public void setElevatorProfile(double goalPositionM) { 252 setElevatorProfile(goalPositionM, 0.0); 253 } 254 255 /** 256 * Creates a new trapezoidal profile for the elevator 257 * 258 * @param goalPositionM The goal position to reach, in meters 259 * @param goalVelocityMPS The goal velocity to reach, in meters per second 260 */ 261 public void setElevatorProfile(double goalPositionM, double goalVelocityMPS) { 262 setElevatorProfile(goalPositionM, goalVelocityMPS, _maxVelocityMPS); 263 } 264 265 /** 266 * Creates a new trapezoidal profile for the elevator 267 * 268 * @param goalPositionM The goal position to reach, in meters 269 * @param goalVelocityMPS The goal velocity to reach, in meters per second 270 * @param maxVelocityMPS The maximum velocity, in meters per second 271 */ 272 public void setElevatorProfile( 273 double goalPositionM, double goalVelocityMPS, double maxVelocityMPS 274 ) { 275 setElevatorProfile(goalPositionM, goalVelocityMPS, maxVelocityMPS, _maxAccelerationMPS2); 276 } 277 278 /** 279 * Creates a new trapezoidal profile for the elevator 280 * 281 * @param goalPositionM The goal position to reach, in meters 282 * @param goalVelocityMPS The goal velocity to reach, in meters per second 283 * @param maxVelocityMPS The maximum velocity, in meters per second 284 * @param maxAccelerationMPS2 The maximum acceleration, in meters per second squared 285 */ 286 public void setElevatorProfile( 287 double goalPositionM, 288 double goalVelocityMPS, 289 double maxVelocityMPS, 290 double maxAccelerationMPS2 291 ) { 292 setElevatorProfile(goalPositionM, goalVelocityMPS, maxVelocityMPS, maxAccelerationMPS2, true); 293 } 294 295 /** 296 * Creates a new trapezoidal profile for the elevator 297 * 298 * @param goalPositionM The goal position to reach, in meters 299 * @param goalVelocityMPS The goal velocity to reach, in meters per second 300 * @param maxVelocityMPS The maximum velocity, in meters per second 301 * @param maxAccelerationMPS2 The maximum acceleration, in meters per second squared 302 * @param setCurrentState True if the profiles current state should base itself off sensor 303 * values rather than continue from the existing state 304 */ 305 public void setElevatorProfile( 306 double goalPositionM, 307 double goalVelocityMPS, 308 double maxVelocityMPS, 309 double maxAccelerationMPS2, 310 boolean setCurrentState 311 ) { 312 if (_isMicrosystemDisabled) { 313 return; 314 } 315 setFollowProfile(false); 316 317 if (setCurrentState) { 318 _curState.position = getElevatorHeightM(); 319 _curState.velocity = getElevatorVelocityMPS(); 320 } 321 322 _goalState.position = 323 AlgebraicUtils.clamp(goalPositionM, _elevatorMinHeightM, _elevatorMaxHeightM); 324 _goalState.velocity = goalVelocityMPS; 325 326 _trapProfile = new TrapezoidProfile( 327 (maxVelocityMPS >= _maxVelocityMPS || maxAccelerationMPS2 >= _maxAccelerationMPS2) 328 ? _elevatorConf.trapezoidalLimits 329 : new TrapezoidProfile.Constraints(maxVelocityMPS, maxAccelerationMPS2) 330 ); 331 332 _profileTimer.restart(); 333 } 334 335 /** 336 * Gets the height of the elevator in meters 337 * 338 * @return height in meters 339 */ 340 public double getElevatorHeightM() { 341 return motorToMeters(getPrimaryMotorPosition()); 342 } 343 344 /** 345 * Gets the velocity of the elevator in meters per second 346 * 347 * @return velocity in meters per second 348 */ 349 public double getElevatorVelocityMPS() { 350 return motorToMeters(getPrimaryMotorVelocity()); 351 } 352 353 /** 354 * Sets the velocity of the elevator in MPS 355 * 356 * @param mps Desired velocity in meters per second 357 * @param withFF with feedforward 358 */ 359 public void setElevatorVelocity(double mps, boolean withFF) { 360 if (_isMicrosystemDisabled) { 361 return; 362 } 363 364 setFollowProfile(false); 365 _primaryMotor.setControl(_requestedVelocityVoltage.withVelocity(metersToMotor(mps)) 366 .withFeedForward(withFF ? _elevatorFF.calculate(mps) : 0.0)); 367 } 368 369 /** 370 * Clamps the elevator position to be within height limits 371 * 372 * @param target goal position of the elevator in meters 373 * @return clamped goal position in meters 374 */ 375 public double clampElevatorPosition(double target) { 376 return AlgebraicUtils.clamp(target, _elevatorMinHeightM, _elevatorMaxHeightM); 377 } 378 379 @Override 380 public void onEnable() { 381 if (_isMicrosystemDisabled) { 382 return; 383 } 384 super.onEnable(); 385 386 if (_isFFTuningMicro) { 387 _elevatorFF = new ElevatorFeedforward( 388 _KSEntry.getDouble(_elevatorFF.getKs()), 389 _KGEntry.getDouble(_elevatorFF.getKg()), 390 _KVEntry.getDouble(_elevatorFF.getKv()), 391 _KAEntry.getDouble(_elevatorFF.getKa()) 392 ); 393 _primaryMotor.setControl(_requestedPositionVoltage.withFeedForward(_elevatorFF.getKs())); 394 } 395 } 396 397 @Override 398 public void onDisable() { 399 if (_isMicrosystemDisabled) { 400 return; 401 } 402 403 super.onDisable(); 404 } 405 406 @Override 407 public void periodic() { 408 if (_isMicrosystemDisabled) { 409 return; 410 } else if (motorResetConfig()) { 411 setFollowProfile(false); 412 setElevatorProfile(getElevatorHeightM(), 0.0); 413 _primaryMotor.set(0.0); 414 } 415 if (_followProfile) { 416 followLastProfile(); 417 } 418 } 419 420 @Override 421 public void simulationInit() { 422 if (_isMicrosystemDisabled) { 423 return; 424 } 425 _elevatorSim = new ElevatorSim( 426 _elevatorConf.motorTypes[0].simSupplier.apply(_elevatorConf.numMotors), 427 _motorToMechRatio, 428 _elevatorConf.carriageMassValue, 429 _elevatorConf.drumDiameter / 2.0, 430 _elevatorMinHeightM, 431 _elevatorMaxHeightM, 432 true, 433 0 434 ); 435 _primaryMotorSim = _primaryMotor.getSimState(); 436 _mechanism = new Mechanism2d(_elevatorConf.mech2dDim, _elevatorConf.mech2dDim); 437 _root = _mechanism.getRoot(_elevatorConf.rootName, _elevatorConf.rootX, _elevatorConf.rootY); 438 _elevatorBaseStageLigament = new MechanismLigament2d( 439 "BaseStage", 440 _elevatorConf.lineLength, 441 _elevatorConf.angle, 442 6.0, 443 new Color8Bit(Color.kAliceBlue) 444 ); 445 _elevatorStage1Ligament = new MechanismLigament2d( 446 "Stage1", 447 _elevatorConf.lineLength, 448 _elevatorConf.angle, 449 6.0, 450 new Color8Bit(Color.kLightSalmon) 451 ); 452 _root.append(_elevatorBaseStageLigament); 453 _root.append(_elevatorStage1Ligament); 454 SmartDashboard.putData("SIM: " + _conf.name, _mechanism); 455 _primaryMotorInverted = _conf.motorDirection[0] == InvertedValue.Clockwise_Positive; 456 } 457 458 @Override 459 public void simulationPeriodic() { 460 if (_isMicrosystemDisabled) { 461 return; 462 } 463 _elevatorSim.setInputVoltage(_primaryMotor.getMotorVoltage().getValueAsDouble()); 464 _elevatorSim.update(TagalongConfiguration.LOOP_PERIOD_S); 465 466 double simAccelMPS2 = (_elevatorSim.getVelocityMetersPerSecond() - _simVelocityMPS) 467 / TagalongConfiguration.LOOP_PERIOD_S; 468 _simVelocityMPS = _elevatorSim.getVelocityMetersPerSecond(); 469 470 _primaryMotorSim.setRawRotorPosition(metersToMotor( 471 _primaryMotorInverted ? (-1 * _elevatorSim.getPositionMeters()) 472 : _elevatorSim.getPositionMeters() 473 )); 474 _primaryMotorSim.setRotorVelocity(metersToMotor( 475 _primaryMotorInverted ? (-1 * _elevatorSim.getVelocityMetersPerSecond()) 476 : _elevatorSim.getVelocityMetersPerSecond() 477 )); 478 _primaryMotorSim.setRotorAcceleration( 479 metersToMotor(_primaryMotorInverted ? (-1 * simAccelMPS2) : simAccelMPS2) 480 ); 481 _elevatorStage1Ligament.setLength(_elevatorSim.getPositionMeters()); 482 _primaryMotorSim.setSupplyVoltage(RobotController.getBatteryVoltage()); 483 RoboRioSim.setVInVoltage( 484 BatterySim.calculateDefaultBatteryLoadedVoltage(_elevatorSim.getCurrentDrawAmps()) 485 ); 486 } 487 488 @Override 489 public void updateShuffleboard() { 490 if (_isMicrosystemDisabled) { 491 return; 492 } 493 if (_isShuffleboardMicro) { 494 _currentPositionEntry.setDouble(getElevatorHeightM()); 495 _currentVelocityEntry.setDouble(getElevatorVelocityMPS()); 496 } 497 } 498 499 /** 500 * Configure shuffleboard for the elevator 501 */ 502 @Override 503 public void configShuffleboard() { 504 if (_isMicrosystemDisabled) { 505 return; 506 } 507 super.configShuffleboard(); 508 } 509 510 /** 511 * Converts motor rotations to elevator height 512 * 513 * @param rotation motor position in rotations 514 * @return elevator position in meters 515 */ 516 public double motorToMeters(double rotation) { 517 return _isMicrosystemDisabled 518 ? 0.0 519 : (rotation * _elevatorConf.drumCircumference) / _motorToMechRatio; 520 } 521 522 /** 523 * Converts elevator height to motor rotations 524 * 525 * @param meter elevator position in meters 526 * @return motor position in rotations 527 */ 528 public double metersToMotor(double meter) { 529 return _isMicrosystemDisabled ? 0.0 530 : (meter / _elevatorConf.drumCircumference) * _motorToMechRatio; 531 } 532 533 /** 534 * Checks whether or not it's safe for the elevator to move 535 * 536 * @return whether or not it's safe to move 537 */ 538 public boolean isSafeToMove() { 539 return true; 540 } 541 542 /** 543 * Bounds checking function that uses the current elevator position 544 * 545 * @param lowerBound minimum of acceptable range 546 * @param upperBound maximum of acceptable range 547 * @return if the current position is in specified acceptable range 548 */ 549 public boolean isElevatorInTolerance(double lowerBound, double upperBound) { 550 if (_isMicrosystemDisabled) { 551 return true; 552 } 553 return AlgebraicUtils.inTolerance(getElevatorHeightM(), lowerBound, upperBound); 554 } 555 556 /** 557 * Sets the position of the elevator in meters 558 * 559 * @param height goal position in meters 560 */ 561 public void setElevatorHeight(double height) { 562 if (_isMicrosystemDisabled) { 563 return; 564 } 565 _primaryMotor.setPosition(metersToMotor(height)); 566 } 567 568 @Override 569 public void holdCurrentPosition() { 570 setElevatorProfile(getElevatorHeightM(), 0.0); 571 setFollowProfile(true); 572 } 573 574 /** 575 * Retrieves base stage ligament attached to elevator Mechanism2d 576 * 577 * @return elevator base stage ligament 578 */ 579 public MechanismLigament2d getElevatorBaseStageLigament() { 580 return _elevatorBaseStageLigament; 581 } 582 583 /** 584 * Retrieves stage 1 ligament attached to elevator Mechanism2d 585 * 586 * @return elevator stage 1 ligament 587 */ 588 public MechanismLigament2d getElevatorStage1Ligament() { 589 return _elevatorStage1Ligament; 590 } 591}