季后赛重构:补完 Superstructure 状态机与射击门控

- Superstructure 增加 INDEXING/HOLDING_NOTE 状态,实现完整状态流转:
  DEPLOYING_INTAKE -> INTAKING -> INDEXING -> HOLDING_NOTE ->
  SPINNING_UP -> AIMING -> READY_TO_SHOOT -> SHOOTING -> CLEANUP
- canShoot 门控补全:atSpeed && isAimed && rotaterAtGoal && hasNote && 底盘近静止
  (hasNote/静止门控均可在 Constants 中旁路)
- 无传感器持球跟踪:INDEXING 完成置位、射击/EJECT 后清空,
  未归零被拦下的 intake 不会误置持球
- DriveControls 在既有 hub 方位角计算处同步 setTargetHeading,
  使 isAimed 门控与 AutoAimming 实际瞄准同源;自动阶段经 pushDistanceData 保持更新
- 移除 Superstructure 基于 limelight tx 的瞄准更新(符号错误且无目标时滞留旧值)
- 背板回零命令下沉到 ShooterSubsystem,OperatorControls 不再直接操作电机
- ShooterSubsystem 清理旧别名与闭环遗留接口,背板独立控制请求
- 射表改为 InterpolatingDoubleTreeMap(Constants.Shooter.flywheelRpsByDistance)
- IntakeRotater:未归零时 atGoal 一律 false,新增 blockedByNotZeroed 遥测;
  atGoal 容差改为非零占位值
- 修复 gradlew 缺少可执行权限

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTEBmjtkz1ouCmn5yytFti
This commit is contained in:
Claude
2026-07-04 12:19:50 +00:00
parent 3c5d17b527
commit 6264ef0762
11 changed files with 288 additions and 158 deletions
@@ -16,14 +16,18 @@ import edu.wpi.first.math.trajectory.TrapezoidProfile.Constraints;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.Encoder;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.Commands;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import frc.robot.Constants;
/**
* Shooter 总成的硬件封装。
*
* 飞轮和 shooter 内部 conveyor 都使用电压开环输出。
* 飞轮和 shooter 内部 conveyor 都使用电压开环输出,
* 右侧电机通过反向 follower 跟随左侧电机。
* 背板控制沿用旧代码中的电机、编码器和 PID 调参流程。
* 何时 spin up、何时喂球由 Superstructure 决定,本类不做比赛动作判断。
*/
public class ShooterSubsystem extends SubsystemBase {
public final TalonFX leftFlywheel;
@@ -31,17 +35,13 @@ public class ShooterSubsystem extends SubsystemBase {
public final TalonFX leftConveyor;
public final TalonFX rightConveyor;
// 保留旧字段别名,让原有 dashboard tuner 在重构后仍能指向左飞轮和左 conveyor。
public final TalonFX flywheelMotorLeft;
public final TalonFX conveyorMotor;
public final TalonFXS backboardMotor;
public final Encoder backboardEncoder;
public final ProfiledPIDController backboardPID;
private final VoltageOut flywheelVoltageRequest = new VoltageOut(0);
private final VoltageOut conveyorVoltageRequest = new VoltageOut(0);
private final VelocityVoltage velocityRequest = new VelocityVoltage(0);
private final VelocityVoltage backboardVelocityRequest = new VelocityVoltage(0);
private final Follower flywheelFollower;
private final Follower conveyorFollower;
@@ -59,9 +59,6 @@ public class ShooterSubsystem extends SubsystemBase {
leftConveyor = new TalonFX(Constants.Shooter.leftConveyorID);
rightConveyor = new TalonFX(Constants.Shooter.rightConveyorID);
flywheelMotorLeft = leftFlywheel;
conveyorMotor = leftConveyor;
leftFlywheel.getConfigurator().apply(Constants.Shooter.flyWheelSlot0Configs);
rightFlywheel.getConfigurator().apply(Constants.Shooter.flyWheelSlot0Configs);
leftConveyor.getConfigurator().apply(Constants.Shooter.conveyorSlot0Configs);
@@ -95,6 +92,10 @@ public class ShooterSubsystem extends SubsystemBase {
shooterNetworkTable = NetworkTableInstance.getDefault().getTable("Shooter");
}
/* ====================== */
/* 飞轮 */
/* ====================== */
public void setRightFollowLeft() {
// 成对机构机械方向相反,因此右侧电机以反向 follower 跟随左侧电机。
rightFlywheel.setControl(flywheelFollower);
@@ -120,16 +121,11 @@ public class ShooterSubsystem extends SubsystemBase {
}
public void setFlywheelVelocityByDistance(double distance) {
// 临时距离映射。拿到真实射击数据后,应替换为实测表或重新调常量。
double clampedDistance = MathUtil.clamp(distance, 1.2, 5.0);
double targetRps = Constants.Shooter.defaultFlywheelTargetRps + (clampedDistance - 2.5) * 4.0;
// 查 Constants 里的射表,两端自动取边界值。
double targetRps = Constants.Shooter.flywheelRpsByDistance.get(distance);
setFlywheelVelocity(targetRps, targetRps);
}
public void setFlywheelSpeedByRPS(double targetSpeed) {
setFlywheelVelocity(targetSpeed, targetSpeed);
}
public void setFlywheelVoltage(double volts) {
targetLeftFlywheelRps = 0.0;
targetRightFlywheelRps = 0.0;
@@ -138,39 +134,10 @@ public class ShooterSubsystem extends SubsystemBase {
setRightFollowLeft();
}
public void runShooterConveyor(double percent) {
shooterConveyorPercent = MathUtil.clamp(percent, -1.0, 1.0);
leftConveyor.setControl(conveyorVoltageRequest.withOutput(shooterConveyorPercent * 12.0));
setRightFollowLeft();
}
public void stopShooterConveyor() {
runShooterConveyor(0.0);
}
public void setConveyorSpeedByRPS(double speed) {
leftConveyor.setControl(velocityRequest.withVelocity(speed));
setRightFollowLeft();
}
public void setConveyorSpeedOpen(double speed) {
runShooterConveyor(speed);
}
public void stopFlywheel() {
setFlywheelVoltage(Constants.Shooter.flywheelIdleVoltage);
}
public void stopAll() {
stopFlywheel();
stopShooterConveyor();
backboardMotor.set(0);
}
public void stopMotors() {
stopAll();
}
public boolean atSpeed() {
// 虽然飞轮控制是电压开环,但仍读取 TalonFX 速度作为喂球门控。
if (targetFlywheelRps <= 1.0) {
@@ -200,13 +167,35 @@ public class ShooterSubsystem extends SubsystemBase {
return targetFlywheelRps;
}
/* ====================== */
/* shooter conveyor */
/* ====================== */
public void runShooterConveyor(double percent) {
shooterConveyorPercent = MathUtil.clamp(percent, -1.0, 1.0);
leftConveyor.setControl(conveyorVoltageRequest.withOutput(shooterConveyorPercent * 12.0));
setRightFollowLeft();
}
public void stopShooterConveyor() {
runShooterConveyor(0.0);
}
public double getConveyorStatorCurrent() {
return leftConveyor.getStatorCurrent().getValueAsDouble();
}
/* ====================== */
/* 背板 */
/* ====================== */
public void setBackboardSpeedByRPS(double speed) {
if (speed > 0 && speed < 10) {
speed = 10;
} else if (speed < 0 && speed > -10) {
speed = -10;
}
backboardMotor.setControl(velocityRequest.withVelocity(speed));
backboardMotor.setControl(backboardVelocityRequest.withVelocity(speed));
}
public void setBackboardPosition(double targetPosition) {
@@ -232,6 +221,20 @@ public class ShooterSubsystem extends SubsystemBase {
setBackboardSpeedByRPS(pidOutput);
}
public void holdBackboardAt(double targetPosition) {
// 上层只给目标位置;到位后停止输出,未到位时继续闭环。
setBackboardPosition(targetPosition);
if (isBackboardAtTarget()) {
backboardMotor.set(0.0);
} else {
outputBackboard();
}
}
public void stopBackboard() {
backboardMotor.set(0.0);
}
public double getBackboardPosition() {
return backboardEncoder.getDistance();
}
@@ -240,28 +243,31 @@ public class ShooterSubsystem extends SubsystemBase {
return backboardPID.atGoal();
}
public void conveyorWaitForAcceleration() {
stopShooterConveyor();
}
public void conveyorRun() {
runShooterConveyor(Constants.Superstructure.shooterFeedPercent);
}
public void resetbackboardencoder() {
backboardEncoder.reset();
backboardPID.reset(0);
}
public double getConveyorStatorCurrent() {
return leftConveyor.getStatorCurrent().getValueAsDouble();
/**
* 背板回零命令:闭环回到 0 位后停止输出。
* 命令留在 subsystem 内,controls 层只负责绑定按键。
*/
public Command homeBackboardCommand() {
return Commands.sequence(
Commands.runOnce(() -> setBackboardPosition(0.0), this),
Commands.run(() -> holdBackboardAt(0.0), this).until(this::isBackboardAtTarget),
Commands.runOnce(this::stopBackboard, this)
);
}
public double getDistanceToHub() {
return NetworkTableInstance.getDefault().getTable("AutoControl").getEntry("distanceToHub").getDouble(0.0);
}
/* ====================== */
/* 整体 */
/* ====================== */
public double getDistanceToPassball() {
return NetworkTableInstance.getDefault().getTable("AutoControl").getEntry("distanceToPassball").getDouble(0.0);
public void stopAll() {
stopFlywheel();
stopShooterConveyor();
stopBackboard();
}
@Override
@@ -270,6 +276,7 @@ public class ShooterSubsystem extends SubsystemBase {
shooterNetworkTable.getEntry("flywheelSpeed").setDouble(getAverageFlywheelVelocity());
shooterNetworkTable.getEntry("leftFlywheelSpeed").setDouble(getLeftFlywheelVelocity());
shooterNetworkTable.getEntry("rightFlywheelSpeed").setDouble(getRightFlywheelVelocity());
shooterNetworkTable.getEntry("flywheelSpeedDifference").setDouble(getFlywheelVelocityDifference());
shooterNetworkTable.getEntry("flywheelTargetSpeed").setDouble(targetFlywheelRps);
shooterNetworkTable.getEntry("flywheelAtSpeed").setBoolean(atSpeed());
shooterNetworkTable.getEntry("shooterConveyorPercent").setDouble(shooterConveyorPercent);
@@ -277,8 +284,6 @@ public class ShooterSubsystem extends SubsystemBase {
shooterNetworkTable.getEntry("backboardCurrentRate").setDouble(getBackboardPosition());
shooterNetworkTable.getEntry("backboardTargetRate").setDouble(backboardPID.getSetpoint().position);
shooterNetworkTable.getEntry("isBackboardAtTarget").setBoolean(isBackboardAtTarget());
shooterNetworkTable.getEntry("distanceGetted").setDouble(getDistanceToHub());
shooterNetworkTable.getEntry("distancetopassball").setDouble(getDistanceToPassball());
shooterNetworkTable.getEntry("statorCurrent").setDouble(getConveyorStatorCurrent());
}
}