// 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. package frc.robot.subsystems; import org.fairportrobotics.frc.posty.TestableSubsystem; import org.fairportrobotics.frc.posty.test.PostTest; import org.littletonrobotics.junction.Logger; import org.littletonrobotics.junction.ConsoleSource.RoboRIO; import static org.fairportrobotics.frc.posty.assertions.Assertions.*; import com.ctre.phoenix6.controls.PositionVoltage; import com.ctre.phoenix6.StatusSignal; import com.ctre.phoenix6.configs.CurrentLimitsConfigs; import com.ctre.phoenix6.configs.FeedbackConfigs; import com.ctre.phoenix6.configs.MotorOutputConfigs; import com.ctre.phoenix6.configs.Slot0Configs; import com.ctre.phoenix6.configs.SoftwareLimitSwitchConfigs; import com.ctre.phoenix6.configs.TalonFXConfiguration; import com.ctre.phoenix6.hardware.TalonFX; import com.ctre.phoenix6.signals.ForwardLimitValue; import com.ctre.phoenix6.signals.InvertedValue; import com.ctre.phoenix6.signals.ReverseLimitValue; import com.ctre.phoenix6.sim.TalonFXSimState; import com.revrobotics.PersistMode; import com.revrobotics.ResetMode; import com.revrobotics.spark.ClosedLoopSlot; import com.revrobotics.spark.FeedbackSensor; import com.revrobotics.spark.SparkClosedLoopController; import com.revrobotics.spark.SparkMax; import com.revrobotics.spark.SparkBase.ControlType; import com.revrobotics.spark.SparkLowLevel.MotorType; import com.revrobotics.spark.config.SparkMaxConfig; import frc.robot.Constants; import frc.robot.Robot; import edu.wpi.first.math.geometry.Pose3d; import edu.wpi.first.math.geometry.Rotation2d; import edu.wpi.first.units.AngleUnit; import edu.wpi.first.units.Units; import edu.wpi.first.units.measure.Angle; import edu.wpi.first.wpilibj.DigitalInput; import edu.wpi.first.wpilibj.Servo; import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.Commands; public class TurretSubsystem extends TestableSubsystem { public enum TurretState { INIT, HOMING, READY; } private final boolean LOGGING = true; private TurretState turretState; private DigitalInput turretLimitSwitch; private TalonFX turretMotor; private PositionVoltage turretControlMode = new PositionVoltage(0); private TalonFXSimState turretSim; private StatusSignal turretMotorPosition; private StatusSignal turretMotorForwardLimit; private StatusSignal turretMotorReverseLimit; private StatusSignal turretPositionError; private double forwardLimit = 0; private double reverseLimit = 0; private SparkMax launcherMotor; private SparkClosedLoopController launcherControler; private double turretOffsetInRotations = 0; private Servo hood; private Pose3d turretTarget; public TurretSubsystem() { // Turret configuration turretLimitSwitch = new DigitalInput(Constants.ShooterConstants.TURRET_LIMIT_CHANNEL); turretMotor = new TalonFX(Constants.ShooterConstants.TURRET_MOTOR_ID); turretMotor.getConfigurator().apply( new TalonFXConfiguration() .withSlot0( new Slot0Configs() .withKP(6) .withKI(0) .withKD(0.3) .withKS(0) ) .withMotorOutput( new MotorOutputConfigs() .withInverted(InvertedValue.Clockwise_Positive) ) .withCurrentLimits( new CurrentLimitsConfigs() .withStatorCurrentLimit(50) ) .withSoftwareLimitSwitch(new SoftwareLimitSwitchConfigs().withForwardSoftLimitEnable(false).withReverseSoftLimitEnable(false)) .withFeedback( new FeedbackConfigs() .withFeedbackRotorOffset(0) ) ); turretMotorPosition = turretMotor.getPosition(); turretMotorForwardLimit = turretMotor.getForwardLimit(); turretMotorReverseLimit = turretMotor.getReverseLimit(); turretPositionError = turretMotor.getClosedLoopError(); turretSim = turretMotor.getSimState(); // Launcher configuration launcherMotor = new SparkMax(Constants.ShooterConstants.LAUNCHER_MOTOR_ID, MotorType.kBrushless); SparkMaxConfig config = new SparkMaxConfig().apply((SparkMaxConfig) SparkMaxConfig.Presets.REV_NEO); config.inverted(Constants.ShooterConstants.LAUNCHER_MOTOR_INVERTED); config.voltageCompensation(10); config.closedLoop.p(0.0001).i(0.000004).d(0.000007); // I = 0.0000001 config.closedLoop.feedForward.kS(0.2).kV(0.000).kA(0.000); // config.closedLoop.allowedClosedLoopError(10, ClosedLoopSlot.kSlot0); config.closedLoop.feedbackSensor(FeedbackSensor.kPrimaryEncoder); config.closedLoop.positionWrappingEnabled(false); // config.closedLoopRampRate(0.5); // config.smartCurrentLimit(5, 50); launcherMotor.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters); launcherControler = launcherMotor.getClosedLoopController(); // Hood configuration hood = new Servo(Constants.ShooterConstants.HOOD_SERVO_CHANNEL); turretState = TurretState.INIT; } public void setLauncher(double speed) { if(speed == 0) { launcherMotor.stopMotor(); return; } launcherControler.setSetpoint(speed, ControlType.kVelocity, ClosedLoopSlot.kSlot0); } public boolean isLauncherUpToSpeed() { // Valid if velcity is within 10% of setpoint. // May need to lower this return true; // return Math.abs(launcherControler.getSetpoint() - launcherMotor.getEncoder().getVelocity()) >= launcherControler.getSetpoint() * 0.03; } public void setTargetElevation(double elev) { double pos = Math.abs((elev - 300)/300); Logger.recordOutput("ShooterHood-Angle", pos); hood.set(pos); } public Command revSpeedCommand() { return this.runOnce(() -> setLauncher(-.5)); } public Command setShooter(int speedRPM){ return Commands.runEnd(() -> { setLauncher(speedRPM); }, () -> { launcherMotor.stopMotor(); }, this); } public void homeTurret() { if(turretState == TurretState.HOMING){ return; } turretMotor.set(Constants.ShooterConstants.HOMING_SPEED); turretState = TurretState.HOMING; } public boolean isTurretReady(){ return turretState == TurretState.READY; } public void turretRotateDir(double speed) { turretMotor.set(speed); } public double getTurretAngleRobotRelative() { return getTurretMotorPosition() / Constants.ShooterConstants.TURRET_GEAR_RATIO; } public void setTurretTargetPose(Pose3d targetPose){ turretTarget = targetPose; } public Pose3d getTurretTargetPose(){ return turretTarget; } private double getTurretMotorPosition(){ return turretMotorPosition.refresh().getValueAsDouble() - turretOffsetInRotations; } @Override public void periodic() { if(turretState == TurretState.HOMING) { while(turretState != TurretState.READY) { long start = System.currentTimeMillis(); while(System.currentTimeMillis()-1000= forwardLimit){ turretSim.setForwardLimit(true); } else { turretSim.setForwardLimit(false); } if(posWithOffset <= reverseLimit){ turretSim.setReverseLimit(true); } else { turretSim.setReverseLimit(false); } } turretMotor.setControl(turretControlMode.withPosition(posWithOffset)); turretSim.setRawRotorPosition(-posWithOffset); } public boolean isTurretAtTarget(){ return turretPositionError.refresh().getValueAsDouble() <= 0.1; } private double getRequestedPosTurretRelative() { return (turretControlMode.getPositionMeasure().in(Units.Rotations) - turretOffsetInRotations) / Constants.ShooterConstants.TURRET_GEAR_RATIO; } @PostTest() public void TurretSubsystem_CANDevicesConnected(){ assertThat(launcherMotor.getFaults().can).as("Launcher motor not connected!").isFalse(); assertThat(turretMotor.isConnected()).as("Turret motor not connected!").isTrue(); } }