Skip to content

Elevator Logic

Full source: TeamXbot2025 ElevatorSubsystem

Overview

The 2025 robot's elevator uses:

  • TalonFX motor with onboard PID
  • LaserCAN distance sensor for position feedback
  • Bottom limit switch for calibration
  • Complimentary filter to fuse motor + laser readings

1. Calibration

The elevator starts uncalibrated. It calibrates when it hits the bottom sensor:

java
// When bottom sensor is triggered, set zero reference
public boolean tryMarkElevatorCalibratedAgainstLowerLimit() {
    var success = trySetLaserCANOffset() && trySetMotorOffset();
    if (success) {
        isCalibrated = true;
    }
    return success;
}

Called in periodic() when the bottom sensor is hit:

java
@Override
public void periodic() {
    // Bandage case: isTouchingBottom flashes true for one tick on startup
    if (this.isTouchingBottom() && periodicTickCounter >= 3 && !isCalibrated()) {
        if (tryMarkElevatorCalibratedAgainstLowerLimit()) {
            setTargetValue(getCurrentValue());
        }
    }
}

2. Sensor Fusion

Combines LaserCAN (accurate but noisy) with motor encoder (smooth but drifts):

java
@Override
public Distance getCurrentValue() {
    return Meters.of(sensorFusionFilter.calculateFilteredValue(
        getCalibratedLaserDistance().map(d -> d.in(Meters)).orElse(Double.MAX_VALUE),
        getCalibratedMotorDistance().in(Meters)
    ));
}

The complimentary filter weights the two sensors (0.5 = equal weight):

java
sensorFusionFilter = new ComplimentaryFilter(pf, this.getPrefix(), true, 0.5);

3. Setting Height Targets

Map game-specific positions to elevator heights:

java
public void setTargetHeight(Landmarks.CoralLevel value) {
    switch (value) {
        case TWO -> setTargetValue(l2Height.get());
        case THREE -> setTargetValue(l3Height.get().plus(trimValue.get()));
        case FOUR -> setTargetValue(l4Height.get().plus(trimValue.get()));
        case CORAL_COLLECTING -> setTargetValue(humanLoadHeight.get());
        case HIGH_ALGAE -> setTargetValue(highAlgaeRemovalHeight.get());
        case LOW_ALGAE -> setTargetValue(lowAlgaeRemovalHeight.get());
        // ...
    }
}

Height presets are stored as properties for easy tuning:

java
l2Height = pf.createPersistentProperty("l2Height", Inches.of(0.25));
l3Height = pf.createPersistentProperty("l3Height", Inches.of(15.25));
l4Height = pf.createPersistentProperty("l4Height", Inches.of(47.5));

4. Motion Magic (Trapezoidal Profile)

Smooth acceleration/deceleration instead of instant power changes:

java
masterMotor.setPositionTarget(
    masterMotor.getPosition().plus(deltaRotations),
    motionMagicEnabled.get()
        ? XCANMotorController.MotorPidMode.TrapezoidalVoltage
        : XCANMotorController.MotorPidMode.Voltage
);

Motion Magic constraints are tunable at runtime:

java
public void configureMotionMagicConstraints() {
    masterMotor.setTrapezoidalProfileAcceleration(
        RadiansPerSecondPerSecond.of(motionMagicAcceleration.get()));
    masterMotor.setTrapezoidalProfileJerk(
        RadiansPerSecondPerSecond.of(motionMagicJerk.get()).per(Second));
}

5. Soft Limits

Prevents the elevator from going past safe bounds:

java
@Override
public void setPower(Double power) {
    if (aboveUpperLimit()) {
        power = MathUtils.constrainDouble(power, -1, powerNearUpperLimitThreshold.get());
    }
    if (belowLowerLimit()) {
        power = MathUtils.constrainDouble(power, powerNearLowerLimitThreshold.get(), 1);
    }
    masterMotor.setVoltage(Volts.of(power * 12));
}

6. Logging

All key values are logged for debugging in AdvantageScope:

java
aKitLog.record("ElevatorTargetHeight-m", elevatorTargetHeight.in(Meters));
aKitLog.record("ElevatorCurrentHeight-m", getCurrentValue().in(Meters));
aKitLog.record("isElevatorCalibrated", isCalibrated());
aKitLog.record("isElevatorMaintainerAtGoal", this.isMaintainerAtGoal());

Built for XBot Robotics Team 488