001/************************* PROJECT RON *************************/ 002/* Copyright (c) 2026 StuyPulse Robotics. All rights reserved. */ 003/* Use of this source code is governed by an MIT-style license */ 004/* that can be found in the repository LICENSE file. */ 005/***************************************************************/ 006package com.stuypulse.robot.subsystems.intake; 007 008import java.util.Optional; 009import java.util.function.BooleanSupplier; 010import com.ctre.phoenix6.controls.ControlRequest; 011import com.ctre.phoenix6.controls.DutyCycleOut; 012import com.ctre.phoenix6.controls.Follower; 013import com.ctre.phoenix6.controls.PositionTorqueCurrentFOC; 014import com.ctre.phoenix6.controls.TorqueCurrentFOC; 015import com.ctre.phoenix6.controls.VoltageOut; 016import com.ctre.phoenix6.hardware.TalonFX; 017import com.ctre.phoenix6.signals.MotorAlignmentValue; 018import com.stuypulse.robot.constants.Motors; 019import com.stuypulse.robot.constants.Ports; 020import com.stuypulse.robot.constants.Settings; 021import com.stuypulse.robot.util.LoggedSignals; 022import com.stuypulse.robot.util.SysId; 023 024import static edu.wpi.first.units.Units.*; 025import edu.wpi.first.units.measure.*; 026 027import dev.doglog.DogLog; 028 029import edu.wpi.first.math.filter.Debouncer; 030import edu.wpi.first.math.filter.Debouncer.DebounceType; 031 032import edu.wpi.first.wpilibj.DigitalInput; 033import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine; 034 035public class IntakeImpl extends Intake { 036 037 private final DigitalInput pivotLimitSwitch; 038 039 private final TalonFX pivotMotor; 040 041 private final TalonFX rollerMotorLeft; 042 043 private final TalonFX rollerMotorRight; 044 045 private final PositionTorqueCurrentFOC positionController; 046 047 private final VoltageOut homingController; 048 049 private final TorqueCurrentFOC pushdownController; 050 051 private final DutyCycleOut rollerController; 052 053 private final Follower followerController; 054 055 private final LoggedSignals pivotSignals; 056 057 private final LoggedSignals rollerSignals; 058 059 private final BooleanSupplier pivotStalling; 060 private final BooleanSupplier leftRollerStalling; 061 private final BooleanSupplier rightRollerStalling; 062 063 private Optional<Voltage> pivotVoltageOverride; 064 065 private final Debouncer leftRollerDebouncer; 066 private final Debouncer rightRollerDebouncer; 067 068 public IntakeImpl() { 069 pivotLimitSwitch = new DigitalInput(Ports.Intake.PIVOT_LIMIT_SWITCH); 070 pivotMotor = new TalonFX(Ports.Intake.INTAKE_PIVOT_MOTOR, Settings.CANBUS); 071 Motors.Intake.PIVOT_CONFIG.configure(pivotMotor); 072 073 // zero it at the up pos 074 pivotMotor.setPosition(Settings.Intake.Pivot.INITIAL_ANGLE); 075 pivotSignals = new LoggedSignals(LoggedSignals.SignalLocation.CANIVORE, "Intake/Pivot/", 076 pivotMotor.getSupplyCurrent(), 077 pivotMotor.getStatorCurrent(), 078 pivotMotor.getVelocity()); 079 080 // leader 081 rollerMotorLeft = new TalonFX(Ports.Intake.INTAKE_ROLLER_MOTOR_LEFT, Settings.CANBUS); 082 rollerMotorRight = new TalonFX(Ports.Intake.INTAKE_ROLLER_MOTOR_RIGHT, Settings.CANBUS); 083 Motors.Intake.LEFT_ROLLER_CONFIG.configure(rollerMotorLeft); 084 Motors.Intake.RIGHT_ROLLER_CONFIG.configure(rollerMotorRight); 085 // until rollers are fixed 086 rollerSignals = new LoggedSignals(LoggedSignals.SignalLocation.CANIVORE, "Intake/Roller/").withMotor( 087 "Intake Roller Left", 088 rollerMotorLeft.getSupplyCurrent(), 089 rollerMotorLeft.getStatorCurrent(), 090 rollerMotorLeft.getVelocity()) 091 .withMotor( 092 "Intake Roller Right", 093 rollerMotorRight.getSupplyCurrent(), 094 rollerMotorRight.getStatorCurrent(), 095 rollerMotorRight.getVelocity()); 096 positionController = new PositionTorqueCurrentFOC(getState().getTargetAngle()); 097 homingController = new VoltageOut(Settings.Intake.Pivot.HOMING_DOWN_VOLTAGE).withEnableFOC(true); 098 pushdownController = new TorqueCurrentFOC(Settings.Intake.Pivot.PUSHDOWN_CURRENT.getAsDouble()); 099 rollerController = new DutyCycleOut(getState().getTargetDutyCycle()).withEnableFOC(true); 100 followerController = new Follower(Ports.Intake.INTAKE_ROLLER_MOTOR_LEFT, MotorAlignmentValue.Opposed); 101 rollerMotorRight.setControl(followerController); 102 pivotStalling = () -> pivotMotor.getStatorCurrent().getValue().gt(Settings.Intake.Pivot.STALL_CURRENT); 103 leftRollerStalling = () -> rollerMotorLeft.getStatorCurrent().getValue().gt(Settings.Intake.Roller.STALL_CURRENT); 104 rightRollerStalling = () -> rollerMotorRight.getStatorCurrent().getValue().gt(Settings.Intake.Roller.STALL_CURRENT); 105 // until rollers are fixed 106 leftRollerDebouncer = new Debouncer(Settings.Intake.Roller.STALL_DEBOUNCE_SEC.in(Seconds), DebounceType.kBoth); 107 rightRollerDebouncer = new Debouncer(Settings.Intake.Roller.STALL_DEBOUNCE_SEC.in(Seconds), DebounceType.kBoth); 108 pivotVoltageOverride = Optional.empty(); 109 } 110 111 // For sysid 112 @Override 113 public void setPivotVoltageOverride(Voltage voltage) { 114 this.pivotVoltageOverride = Optional.of(voltage); 115 } 116 117 /** ****************** */ 118 /** Pivot Commands ** */ 119 /** ****************** */ 120 121 @Override 122 public boolean limitSwitchHit() { 123 return !pivotLimitSwitch.get(); 124 } 125 126 @Override 127 public Angle getRelativePosition() { 128 return pivotMotor.getPosition().getValue(); 129 } 130 131 @Override 132 public void seedPivotAngle(Angle angle) { 133 pivotMotor.setPosition(angle); 134 } 135 136 private boolean pivotStalling() { 137 return pivotStalling.getAsBoolean(); 138 } 139 140 /** ******************* */ 141 /** Roller Commands ** */ 142 /** ******************* */ 143 // until rollers are fixed 144 @Override 145 public AngularVelocity getRollerVelocity() { 146 return rollerMotorRight.getVelocity().getValue(); 147 } 148 149 private boolean getLeftRollerStalling() { 150 return leftRollerDebouncer.calculate(leftRollerStalling.getAsBoolean()); 151 } 152 153 private boolean getRightRollerStalling() { 154 return rightRollerDebouncer.calculate(rightRollerStalling.getAsBoolean()); 155 } 156 157 @Override 158 protected void stopRollerMotors() { 159 rollerMotorLeft.stopMotor(); 160 rollerMotorRight.stopMotor(); 161 // re-add the follow control after stopMotor removes it 162 rollerMotorRight.setControl(followerController); 163 } 164 165 @Override 166 protected void stopPivotMotor() { 167 pivotMotor.stopMotor(); 168 } 169 170 private ControlRequest getPivotControl(IntakeState currentState) { 171 return switch (currentState) { 172 case INTAKE, OUTTAKE, DOWN -> pushdownController; //(pivotAboveThreshold) // Hardcoding pushdown disabled temporarily - AZ 173 // ? pushdownController.withOutput(Settings.Intake.Pivot.PUSHDOWN_CURRENT.getAsDouble()) 174 // : positionController.withPosition(currentState.getTargetAngle()).withSlot(0); 175 case HOMING_DOWN -> homingController; 176 case AGITATE, AGITATE_DOWN -> positionController.withPosition(currentState.getTargetAngle()).withSlot(1); 177 default -> positionController.withPosition(currentState.getTargetAngle()).withSlot(0); 178 }; 179 } 180 181 @Override 182 public void periodic() { 183 if (!Settings.EnabledSubsystems.INTAKE.get()) { 184 stopAllMotors(); 185 186 return; 187 } 188 189 if (pivotVoltageOverride.isPresent()) { 190 pivotMotor.setVoltage(pivotVoltageOverride.get().in(Volts)); 191 return; 192 } 193 194 // Input 195 final boolean pivotStalling = pivotStalling(); 196 final boolean leftRollerStalling = getLeftRollerStalling(); 197 final boolean rightRollerStalling = getRightRollerStalling(); 198 final IntakeState currentState = getState(); 199 200 // State 201 if (limitSwitchHit()) { 202 seedPivotAngle(Settings.Intake.Pivot.DEPLOY_ANGLE); 203 } 204 205 if (currentState == IntakeState.HOMING_DOWN && (pivotStalling || limitSwitchHit())) { 206 seedPivotAngle(Settings.Intake.Pivot.DEPLOY_ANGLE); 207 setState(IntakeState.INTAKE); 208 } 209 if ((currentState == IntakeState.DOWN) && (pivotStalling || limitSwitchHit())) { 210 seedPivotAngle(Settings.Intake.Pivot.DEPLOY_ANGLE); 211 } 212 213 // Output 214 final ControlRequest pivotControl = getPivotControl(currentState); 215 final DutyCycleOut rollerControl = rollerController.withOutput(currentState.getTargetDutyCycle()); 216 217 // Apply 218 pivotMotor.setControl(pivotControl); 219 220 rollerMotorLeft.setControl(rollerControl); 221 222 // Logging 223 this.pivotSignals.logAll(); 224 this.rollerSignals.logAll(); 225 DogLog.forceNt.log("Intake/Rollers/Stalling", leftRollerStalling || rightRollerStalling); 226 DogLog.forceNt.log( 227 "Intake/Pivot/Pushing Down", pivotMotor.getAppliedControl() == pushdownController); 228 super.periodic(); 229 } 230 231 @Override 232 public SysIdRoutine getIntakeSysIdRoutine() { 233 return SysId.getRoutine( 234 Settings.Intake.Pivot.RAMP_RATE, 235 Settings.Intake.Pivot.STEP_VOLTAGE, 236 "Intake", 237 this::setPivotVoltageOverride, 238 () -> pivotMotor.getPosition().getValue(), 239 () -> pivotMotor.getVelocity().getValue(), 240 () -> pivotMotor.getMotorVoltage().getValue(), 241 getInstance()); 242 } 243}