Adds adjustable hood as a subsystem and its elastic properties - #121
Adds adjustable hood as a subsystem and its elastic properties#121NRG948DriverStation wants to merge 3 commits into
Conversation
Co-Authored-By: Space9999 <sboddams@gmail.com>
Co-Authored-By: Space9999 <sboddams@gmail.com>
…8/NRGRobot2026 into Programming/AdjustableHood
There was a problem hiding this comment.
Pull request overview
Adds a new AdjustableHood manipulator subsystem and wires it into the robot’s subsystem container and the “Pass” shooting routine, along with Elastic dashboard configuration for testing.
Changes:
- Introduces
AdjustableHoodsubsystem with dashboard widgets/commands and basic angle control loop. - Registers the hood in
Subsystemsand includes it in the manipulators list. - Updates
ShootingCommands.pass()to set the hood to a passing angle and restore to stowed at the end; adds Elastic Testing tab layout.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/frc/robot/subsystems/Subsystems.java | Adds hood instance and includes it in the manipulators collection. |
| src/main/java/frc/robot/subsystems/AdjustableHood.java | New hood subsystem implementation (motor, telemetry, dashboard bindings, control loop). |
| src/main/java/frc/robot/Constants.java | Adds CAN ID constant for adjustable hood motor. |
| src/main/java/frc/robot/commands/ShootingCommands.java | Integrates hood behavior into the pass command. |
| src/main/deploy/elastic-testing.json | Adds Elastic dashboard tab/widgets for Adjustable Hood testing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public static final int SHOOTER_UPPER_RIGHT_ID = 18; | ||
| public static final int SHOOTER_LOWER_LEFT_ID = 17; | ||
| public static final int SHOOTER_UPPER_LEFT_ID = 19; | ||
| public static final int ADJUSTABLE_HOOD_ID = 99; // TODO: Change ID |
There was a problem hiding this comment.
ADJUSTABLE_HOOD_ID is set to 99, which is outside the valid FRC CAN device ID range (0–63) used by CTRE devices. This will prevent the TalonFX from being constructed/communicated with; update this to a real, in-range ID (and ensure it doesn’t collide with existing IDs).
| public static final int ADJUSTABLE_HOOD_ID = 99; // TODO: Change ID | |
| public static final int ADJUSTABLE_HOOD_ID = 20; |
| private static final MotorParameters MOTOR_PARAMS = | ||
| RobotPreferences.ROBOT_TYPE.selectOrDefault( | ||
| Map.of( | ||
| RobotSelector.CompetitionRobot2026, MotorParameters.KrakenX60, | ||
| RobotSelector.PracticeRobot2026, MotorParameters.NullMotor), | ||
| MotorParameters.NullMotor); | ||
|
|
||
| private static final double TOLERANCE = Units.degreesToRadians(2.0); | ||
| private static final double STALL_VOLTAGE = 0.1; // TODO: Change values | ||
| private static final double STALL_VELOCITY = Units.degreesToRadians(1); | ||
|
|
||
| private static final double GEAR_RATIO = 25; | ||
| private static final double RADIANS_PER_ROTATION = (2 * Math.PI) / GEAR_RATIO; | ||
| private static final double MAX_VELOCITY = | ||
| (RADIANS_PER_ROTATION * (MOTOR_PARAMS.getFreeSpeedRPM()) / 60.0); | ||
|
|
||
| public static final double STOW_ANGLE = Units.degreesToRadians(75); // TODO: Change angles | ||
| public static final double PASSING_ANGLE = Units.degreesToRadians(50); | ||
| public static final double MIN_ANGLE = PASSING_ANGLE; // TODO: Change angles | ||
| public static final double MAX_ANGLE = STOW_ANGLE; | ||
|
|
||
| public static final double KS = MOTOR_PARAMS.getKs(); | ||
| public static final double KV = (MAX_BATTERY_VOLTAGE - KS) / MAX_VELOCITY; | ||
| private final SimpleMotorFeedforward feedforwardController; | ||
|
|
||
| private final TalonFX talonFX = new TalonFX(ADJUSTABLE_HOOD_ID); | ||
| // TODO: Determine direction | ||
| private final TalonFXAdapter motor = | ||
| new TalonFXAdapter( | ||
| "/AdjustableHood/Motor", talonFX, CLOCKWISE_POSITIVE, BRAKE, RADIANS_PER_ROTATION); | ||
|
|
There was a problem hiding this comment.
This subsystem selects MotorParameters.NullMotor for some robot types, but still unconditionally instantiates a TalonFX and TalonFXAdapter. That defeats the purpose of NullMotor selection and can cause CAN timeouts/log spam (or runtime issues) on robots without the hood hardware. Consider constructing the motor via MOTOR_PARAMS.newController(...) and using the MotorController interface (or remove the NullMotor selection if the hood is always present).
| double feedforward = feedforwardController.calculate(goalAngle); | ||
| double feedback = pidController.calculate(currentAngle, goalAngle); | ||
| double voltage = feedforward + feedback; |
There was a problem hiding this comment.
SimpleMotorFeedforward.calculate(...) expects a velocity setpoint (and optional acceleration), but this code passes goalAngle (a position in radians). This is a units/logic bug and will generate incorrect voltages. If you’re doing position control, either (a) use an appropriate position/arm feedforward model (e.g., ArmFeedforward with kG term for gravity) and calculate based on angle/velocity, or (b) remove the feedforward and rely on PID until a correct feedforward model is implemented.
| double feedforward = feedforwardController.calculate(goalAngle); | |
| double feedback = pidController.calculate(currentAngle, goalAngle); | |
| double voltage = feedforward + feedback; | |
| double voltage = pidController.calculate(currentAngle, goalAngle); |
| // Check if the mechanism is stalled | ||
| if (Math.abs(voltage) > STALL_VOLTAGE && Math.abs(encoder.getVelocity()) < STALL_VELOCITY) { | ||
| voltage = 0; | ||
| } | ||
| motor.setVoltage(voltage); |
There was a problem hiding this comment.
The stall detection will zero the commanded voltage whenever |encoder velocity| is below the threshold, which is true at the start of any move. Since voltage is then forced to 0, the mechanism may never begin moving. Stall detection usually needs a time component (e.g., only after N ms of low velocity while commanding significant voltage) and/or current-based detection, rather than a single-cycle check.
| private void resetHoodAngle(double angleRadians) { | ||
| encoder.setPosition(angleRadians); | ||
| goalAngle = angleRadians; | ||
| logCurrentAngle.append(currentAngle); | ||
| logGoalAngle.append(goalAngle); | ||
| } |
There was a problem hiding this comment.
resetHoodAngle() appends currentAngle to the log but never updates currentAngle to match the encoder position it just set, so initial telemetry/logging will be wrong until the next periodic update. Set currentAngle = angleRadians (or read back encoder position) before logging, and consider also appending current/goal angle from updateTelemetry() if these logs are intended to be time-series telemetry.
| } | ||
|
|
||
| /** | ||
| * {@return the current intake arm angle in degrees} The angle is relative to horizontal with |
There was a problem hiding this comment.
These Javadocs appear to be copy/pasted from IntakeArm (e.g., references to “intake arm” and “degress”), which makes the public API misleading. Please update the descriptions/return text to refer to the hood and correct the spelling (“degrees”).
| * {@return the current intake arm angle in degrees} The angle is relative to horizontal with | |
| * {@return the current hood angle in degrees} The angle is relative to horizontal with |
| AdjustableHood hood = subsystems.hood; | ||
|
|
||
| return Commands.parallel( | ||
| Commands.runOnce(hood::setPassingAngle), |
There was a problem hiding this comment.
Commands.runOnce(hood::setPassingAngle) is created without requiring the hood subsystem, so the scheduler won’t prevent other hood commands from running concurrently and stomping the goal state. Consider using Commands.runOnce(hood::setPassingAngle, hood) (and similarly ensure the stow action in finallyDo can’t conflict) so hood ownership is explicit.
| Commands.runOnce(hood::setPassingAngle), | |
| Commands.runOnce(hood::setPassingAngle, hood), |
|
I wonder if we should re-use the |
No description provided.