修复审查发现的自动赛与状态机问题

- rotater 开机默认按收拢姿态归零(assumeZeroedOnBoot,可关),
  否则自动赛无人能按归零键,收球和射击被未归零保护永久拦下
- 预装球开机默认持球(assumePreloadedAtBoot,可关),
  否则自动赛第一发被 hasNote 门控拦死
- 新增操作员 X 键运行时翻转持球状态,纠正无传感器推断的失真
- 进入禁用前结算持球推断:SHOOTING 中被禁用视为球已射出,
  INDEXING 中被禁用视为球已入传送带
- 未归零被拦下的 intake 转射击不再误置持球
- CLEANUP 结束不再强写 wantedState=IDLE,清理窗口内重按射击键不会被吞
- 机构请求按键加 teleop 门控,防止自动赛误触取消 PathPlanner 自动程序

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:30:02 +00:00
parent 6264ef0762
commit 9da20a108c
6 changed files with 54 additions and 9 deletions
@@ -23,7 +23,8 @@ public class ConveyorSubsystem extends SubsystemBase {
private final NetworkTable table = NetworkTableInstance.getDefault().getTable("Conveyor");
private double voltage = 0.0;
private boolean notePresent = false;
// 开局带预装球时默认持球,否则自动赛第一发会被 hasNote 门控拦死。
private boolean notePresent = Constants.Superstructure.assumePreloadedAtBoot;
public ConveyorSubsystem() {
mainConveyor = new TalonFX(Constants.Conveyor.mainConveyorID);
@@ -51,6 +51,15 @@ public class IntakeRotaterSubsystem extends SubsystemBase {
rightIntakeRotater.getConfigurator().apply(Constants.Intaker.intakeRotaterSlot0Configs);
rightFollower = new Follower(leftIntakeRotater.getDeviceID(), MotorAlignmentValue.Opposed).withUpdateFreqHz(50);
followLeft();
if (Constants.Intaker.assumeZeroedOnBoot) {
// 开机默认机构处于收拢姿态,直接把当前位置记为零点,
// 让自动赛阶段的收球/射击门控可以工作;只标定编码器,不命令运动。
leftIntakeRotater.setPosition(0.0);
rightIntakeRotater.setPosition(0.0);
zeroed = true;
goal = IntakePosition.STOW;
}
}
public void setGoal(IntakePosition position) {
@@ -131,6 +131,12 @@ public class Superstructure extends SubsystemBase {
return Commands.runOnce(this::requestManual, this);
}
public Command toggleNotePresentCommand() {
// 持球状态是推断值,中途 disable 或漏吸球都可能让它失真;
// 给操作员一个运行时纠正入口(X 键),不用重启机器人。
return Commands.runOnce(() -> conveyor.setNotePresent(!conveyor.hasNote()));
}
public WantedState getWantedState() {
return wantedState;
}
@@ -188,6 +194,13 @@ public class Superstructure extends SubsystemBase {
public void periodic() {
// 禁用状态优先级最高,防止旧按钮输入或自动请求在禁用后继续驱动机构。
if (DriverStation.isDisabled()) {
// 进入禁用前先结算持球推断,避免中途 disable 留下失真的 hasNote:
// 喂球中被禁用视为球已交给飞轮;收纳中被禁用视为球已在传送带里。
if (systemState == SystemState.SHOOTING) {
conveyor.setNotePresent(false);
} else if (systemState == SystemState.INDEXING) {
conveyor.setNotePresent(true);
}
setSystemState(SystemState.DISABLED);
stopAllMechanisms();
publishTelemetry();
@@ -307,9 +320,11 @@ public class Superstructure extends SubsystemBase {
&& systemState != SystemState.SHOOTING
&& systemState != SystemState.CLEANUP) {
// 从 intake 流程直接转射击:没有传感器,默认球已吸入。
if (systemState == SystemState.DEPLOYING_INTAKE
// 未归零被安全逻辑拦下时滚轮从未启动,不可能有球,不置位。
if ((systemState == SystemState.DEPLOYING_INTAKE
|| systemState == SystemState.INTAKING
|| systemState == SystemState.INDEXING) {
|| systemState == SystemState.INDEXING)
&& !intakeRotater.isBlockedByNotZeroed()) {
conveyor.setNotePresent(true);
}
setSystemState(SystemState.SPINNING_UP);
@@ -342,8 +357,8 @@ public class Superstructure extends SubsystemBase {
conveyor.stop();
shooter.stopShooterConveyor();
if (timeInState() > Constants.Superstructure.cleanupSeconds) {
// 一次按键完成一次射击;再射需要重新请求。
wantedState = WantedState.IDLE;
// 不强行改写 wantedState:清理窗口内重按/按住射击键的请求不会被吞掉,
// 而 hasNote 已清空,门控自然挡住空喂球,只会保持飞轮转速待命。
setSystemState(conveyor.hasNote() ? SystemState.HOLDING_NOTE : SystemState.STOWED);
}
return;