1 Commits

3 changed files with 168 additions and 45 deletions

View File

@@ -305,33 +305,32 @@ public class Spindexer {
// Find Next Open Position and start movement // Find Next Open Position and start movement
double currentSpindexerPos = servos.getSpinPos(); double currentSpindexerPos = servos.getSpinPos();
double commandedtravelDistance = 2.0; double commandedtravelDistance = 2.0;
double proposedTravelDistance = Math.abs(intakePositions[0] - currentSpindexerPos); //double proposedTravelDistance = Math.abs(intakePositions[0] - currentSpindexerPos);
if (ballPositions[0].isEmpty && (proposedTravelDistance < commandedtravelDistance)) { //if (ballPositions[0].isEmpty && (proposedTravelDistance < commandedtravelDistance)) {
if (ballPositions[0].isEmpty) {
// Position 1 // Position 1
commandedIntakePosition = 0; commandedIntakePosition = 0;
servos.setSpinPos(intakePositions[commandedIntakePosition]); servos.setSpinPos(intakePositions[commandedIntakePosition]);
currentIntakeState = Spindexer.IntakeState.MOVING; currentIntakeState = Spindexer.IntakeState.MOVING;
commandedtravelDistance = proposedTravelDistance;
} }
proposedTravelDistance = Math.abs(intakePositions[1] - currentSpindexerPos); //proposedTravelDistance = Math.abs(intakePositions[1] - currentSpindexerPos);
if (ballPositions[1].isEmpty && (proposedTravelDistance < commandedtravelDistance)) { //if (ballPositions[1].isEmpty && (proposedTravelDistance < commandedtravelDistance)) {
if (ballPositions[1].isEmpty) {
// Position 2 // Position 2
commandedIntakePosition = 1; commandedIntakePosition = 1;
servos.setSpinPos(intakePositions[commandedIntakePosition]); servos.setSpinPos(intakePositions[commandedIntakePosition]);
currentIntakeState = Spindexer.IntakeState.MOVING; currentIntakeState = Spindexer.IntakeState.MOVING;
commandedtravelDistance = proposedTravelDistance;
} }
proposedTravelDistance = Math.abs(intakePositions[2] - currentSpindexerPos); //proposedTravelDistance = Math.abs(intakePositions[2] - currentSpindexerPos);
if (ballPositions[2].isEmpty && (proposedTravelDistance < commandedtravelDistance)) { if (ballPositions[2].isEmpty) {
// Position 3 // Position 3
commandedIntakePosition = 2; commandedIntakePosition = 2;
servos.setSpinPos(intakePositions[commandedIntakePosition]); servos.setSpinPos(intakePositions[commandedIntakePosition]);
currentIntakeState = Spindexer.IntakeState.MOVING; currentIntakeState = Spindexer.IntakeState.MOVING;
commandedtravelDistance = proposedTravelDistance;
} }
if (currentIntakeState != Spindexer.IntakeState.MOVING) { if (currentIntakeState != Spindexer.IntakeState.MOVING) {
// Full // Full
commandedIntakePosition = bestFitMotif(); //commandedIntakePosition = bestFitMotif();
currentIntakeState = Spindexer.IntakeState.FULL; currentIntakeState = Spindexer.IntakeState.FULL;
} }
moveSpindexerToPos(intakePositions[commandedIntakePosition]); moveSpindexerToPos(intakePositions[commandedIntakePosition]);

View File

@@ -14,6 +14,8 @@ public class Targeting {
double unitConversionFactor = 0.95; double unitConversionFactor = 0.95;
int tileSize = 24; //inches int tileSize = 24; //inches
public final int TILE_UPPER_QUARTILE = 18;
public final int TILE_LOWER_QUARTILE = 6;
public double robotInchesX, robotInchesY = 0.0; public double robotInchesX, robotInchesY = 0.0;
@@ -100,6 +102,60 @@ public class Targeting {
int gridX = Math.abs(Math.floorDiv((int) robotInchesX, tileSize) + 1); int gridX = Math.abs(Math.floorDiv((int) robotInchesX, tileSize) + 1);
int gridY = Math.abs(Math.floorDiv((int) robotInchesY, tileSize)); int gridY = Math.abs(Math.floorDiv((int) robotInchesY, tileSize));
int remX = Math.floorMod((int)robotInchesX, tileSize);
int remY = Math.floorMod((int)robotInchesX, tileSize);
// Determine if we need to interpolate based on tile position.
// if near upper or lower quarter or tile interpolate with next tile.
int x1 = 0;
int y1 = 0;
// interpolate = false;
// if ((remX > TILE_UPPER_QUARTILE) && (remY > TILE_UPPER_QUARTILE) &&
// (robotGridX < 5) && (robotGridY <5)) {
// // +X, +Y
// interpolate = true;
// x1 = robotGridX + 1;
// y1 = robotGridY + 1;
// } else if ((remX < TILE_LOWER_QUARTILE) && (remY < TILE_LOWER_QUARTILE) &&
// (robotGridX > 0) && (robotGridY > 0)) {
// // -X, -Y
// interpolate = true;
// x1 = robotGridX - 1;
// y1 = robotGridY - 1;
// } else if ((remX > TILE_UPPER_QUARTILE) && (remY < TILE_LOWER_QUARTILE) &&
// (robotGridX < 5) && (robotGridY > 0)) {
// // +X, -Y
// interpolate = true;
// x1 = robotGridX + 1;
// y1 = robotGridY - 1;
// } else if ((remX < TILE_LOWER_QUARTILE) && (remY > TILE_UPPER_QUARTILE) &&
// (robotGridX > 0) && (robotGridY < 5)) {
// // -X, +Y
// interpolate = true;
// x1 = robotGridX - 1;
// y1 = robotGridY + 1;
// } else if ((remX < TILE_LOWER_QUARTILE) && (robotGridX > 0)) {
// // -X, Y
// interpolate = true;
// x1 = robotGridX - 1;
// y1 = robotGridY;
// } else if ((remY < TILE_LOWER_QUARTILE) && (robotGridY > 0)) {
// // X, -Y
// interpolate = true;
// x1 = robotGridX;
// y1 = robotGridY - 1;
// } else if ((remX > TILE_UPPER_QUARTILE) && (robotGridX < 5)) {
// // +X, Y
// interpolate = true;
// x1 = robotGridX + 1;
// y1 = robotGridY;
// } else if ((remY > TILE_UPPER_QUARTILE) && (robotGridY < 5)) {
// // X, +Y
// interpolate = true;
// x1 = robotGridX;
// y1 = robotGridY + 1;
// }
//clamp //clamp
robotGridX = Math.max(0, Math.min(gridX, KNOWNTARGETING[0].length - 1)); robotGridX = Math.max(0, Math.min(gridX, KNOWNTARGETING[0].length - 1));
robotGridY = Math.max(0, Math.min(gridY, KNOWNTARGETING.length - 1)); robotGridY = Math.max(0, Math.min(gridY, KNOWNTARGETING.length - 1));
@@ -115,9 +171,9 @@ public class Targeting {
// bilinear interpolation // bilinear interpolation
int x0 = robotGridX; int x0 = robotGridX;
int x1 = Math.min(x0 + 1, KNOWNTARGETING[0].length - 1); //int x1 = Math.min(x0 + 1, KNOWNTARGETING[0].length - 1);
int y0 = gridY; int y0 = robotGridY;
int y1 = Math.min(y0 + 1, KNOWNTARGETING.length - 1); //int y1 = Math.min(y0 + 1, KNOWNTARGETING.length - 1);
double x = (robotInchesX - (x0 * tileSize)) / tileSize; double x = (robotInchesX - (x0 * tileSize)) / tileSize;
double y = (robotInchesY - (y0 * tileSize)) / tileSize; double y = (robotInchesY - (y0 * tileSize)) / tileSize;

View File

@@ -2,9 +2,12 @@ package org.firstinspires.ftc.teamcode.utils;
import static org.firstinspires.ftc.teamcode.constants.Color.redAlliance; import static org.firstinspires.ftc.teamcode.constants.Color.redAlliance;
import static java.lang.Math.abs;
import com.acmerobotics.dashboard.config.Config; import com.acmerobotics.dashboard.config.Config;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry; import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.acmerobotics.roadrunner.Pose2d; import com.acmerobotics.roadrunner.Pose2d;
import com.arcrobotics.ftclib.controller.PIDController;
import com.qualcomm.hardware.limelightvision.LLResult; import com.qualcomm.hardware.limelightvision.LLResult;
import com.qualcomm.hardware.limelightvision.LLResultTypes; import com.qualcomm.hardware.limelightvision.LLResultTypes;
import com.qualcomm.hardware.limelightvision.Limelight3A; import com.qualcomm.hardware.limelightvision.Limelight3A;
@@ -43,9 +46,17 @@ public class Turret {
private int obeliskID = 0; private int obeliskID = 0;
private double offset = 0.0; private double offset = 0.0;
private double currentTrackOffset = 0.0;
private int currentTrackCount = 0;
private double permanentOffset = 0.0; private double permanentOffset = 0.0;
LLResult result;
private PIDController bearingPID;
public static double B_PID_P = 0.3, B_PID_I = 0.0, B_PID_D = 0.05;
boolean bearingAligned = false;
public Turret(Robot rob, MultipleTelemetry tele, Limelight3A cam) { public Turret(Robot rob, MultipleTelemetry tele, Limelight3A cam) {
this.TELE = tele; this.TELE = tele;
this.robot = rob; this.robot = rob;
@@ -56,6 +67,7 @@ public class Turret {
} else { } else {
webcam.pipelineSwitch(2); webcam.pipelineSwitch(2);
} }
bearingPID = new PIDController(B_PID_P, B_PID_I, B_PID_D);
} }
public void zeroTurretEncoder() { public void zeroTurretEncoder() {
@@ -79,12 +91,12 @@ public class Turret {
private void limelightRead() { // only for tracking purposes, not general reads private void limelightRead() { // only for tracking purposes, not general reads
if (redAlliance) { if (redAlliance) {
webcam.pipelineSwitch(3); webcam.pipelineSwitch(4);
} else { } else {
webcam.pipelineSwitch(2); webcam.pipelineSwitch(2);
} }
LLResult result = webcam.getLatestResult(); result = webcam.getLatestResult();
if (result != null) { if (result != null) {
if (result.isValid()) { if (result.isValid()) {
tx = result.getTx(); tx = result.getTx();
@@ -149,6 +161,47 @@ public class Turret {
Param @deltaPos = Pose2d when subtracting robot x, y, heading from goal x, y, heading Param @deltaPos = Pose2d when subtracting robot x, y, heading from goal x, y, heading
*/ */
private double bearingAlign (LLResult llResult) {
double bearingOffset = 0.0;
double targetTx = llResult.getTx(); // How far left or right the target is (degrees)
final double MIN_OFFSET_POWER = 0.15;
final double TARGET_POSITION_TOLERANCE = 1.0;
// LL has 54.5 degree total Horizontal FOV; very edges are not useful.
final double HORIZONTAL_FOV_RANGE = 26.0; // Total usable horizontal degrees from center +/-
final double DRIVE_POWER_REDUCTION = 2.0;
if (abs(targetTx) < TARGET_POSITION_TOLERANCE) {
bearingAligned = true;
} else {
bearingAligned = false;
}
// Only with valid data and if too far off target
if (llResult.isValid() && !bearingAligned)
{
// Adjust Robot Speed based on how far the target is located
// Only drive at half speed max
// switched to PID but original formula left for reference in comments
//drivePower = targetTx/HORIZONTAL_FOV_RANGE / DRIVE_POWER_REDUCTION;
bearingOffset = -(bearingPID.calculate(targetTx, 0.0));
// // Make sure we have enough power to actually drive the wheels
// if (abs(bearingOffset) < MIN_OFFSET_POWER) {
// if (bearingOffset > 0.0) {
// bearingOffset = MIN_OFFSET_POWER;
// } else {
// bearingOffset = -MIN_OFFSET_POWER;
// }
//
// }
}
return bearingOffset;
}
public void trackGoal(Pose2d deltaPos) { public void trackGoal(Pose2d deltaPos) {
/* ---------------- FIELD → TURRET GEOMETRY ---------------- */ /* ---------------- FIELD → TURRET GEOMETRY ---------------- */
@@ -171,41 +224,53 @@ public class Turret {
/* ---------------- LIMELIGHT VISION CORRECTION ---------------- */ /* ---------------- LIMELIGHT VISION CORRECTION ---------------- */
double tagBearingDeg = getBearing(); // + = target is to the left // Update local limelight results
boolean hasValidTarget = (tagBearingDeg != 1000.0); //double tagBearingDeg = getBearing(); // + = target is to the left
//boolean hasValidTarget = (tagBearingDeg != 1000.0);
turretAngleDeg += permanentOffset; turretAngleDeg += permanentOffset;
limelightRead();
// Active correction if we see the target // Active correction if we see the target
if (hasValidTarget && !lockOffset) { if (result.isValid() && !lockOffset) {
double bearingError = Math.abs(tagBearingDeg); currentTrackOffset += bearingAlign(result);
currentTrackCount++;
if (bearingError > cameraBearingEqual) { // double bearingError = Math.abs(tagBearingDeg);
// Apply sqrt scaling to reduce aggressive corrections at large errors //
double filteredBearing = Math.signum(tagBearingDeg) * Math.sqrt(Math.abs(tagBearingDeg)); // if (bearingError > cameraBearingEqual) {
// // Apply sqrt scaling to reduce aggressive corrections at large errors
// Calculate correction // double filteredBearing = Math.signum(tagBearingDeg) * Math.sqrt(Math.abs(tagBearingDeg));
double offsetChange = visionCorrectionGain * filteredBearing; //
// // Calculate correction
// Limit rate of change to prevent jumps // double offsetChange = visionCorrectionGain * filteredBearing;
offsetChange = Math.max(-maxOffsetChangePerCycle, //
Math.min(maxOffsetChangePerCycle, offsetChange)); // // Limit rate of change to prevent jumps
// offsetChange = Math.max(-maxOffsetChangePerCycle,
// Accumulate the correction // Math.min(maxOffsetChangePerCycle, offsetChange));
offset += offsetChange; //
// // Accumulate the correction
TELE.addData("Bearing Error", tagBearingDeg); // offset += offsetChange;
TELE.addData("Offset Change", offsetChange); //
TELE.addData("Total Offset", offset); // TELE.addData("Bearing Error", tagBearingDeg);
} else { // TELE.addData("Offset Change", offsetChange);
// When centered, lock in the learned offset // TELE.addData("Total Offset", offset);
permanentOffset = offset; // } else {
offset = 0.0; // // When centered, lock in the learned offset
} // permanentOffset = offset;
// offset = 0.0;
// }
} else {
// only store perma update after 20+ successful tracks
// this did not work good in testing; only current works best so far.
// if (currentTrackCount > 20) {
// offset = currentTrackOffset;
// }
currentTrackOffset = 0.0;
currentTrackCount = 0;
} }
// Apply accumulated offset // Apply accumulated offset
turretAngleDeg += offset; turretAngleDeg += offset + currentTrackOffset;
/* ---------------- ANGLE → SERVO POSITION ---------------- */ /* ---------------- ANGLE → SERVO POSITION ---------------- */
@@ -236,7 +301,10 @@ public class Turret {
TELE.addData("Target Pos", "%.3f", targetTurretPos); TELE.addData("Target Pos", "%.3f", targetTurretPos);
TELE.addData("Current Pos", "%.3f", currentPos); TELE.addData("Current Pos", "%.3f", currentPos);
TELE.addData("Commanded Pos", "%.3f", turretPos); TELE.addData("Commanded Pos", "%.3f", turretPos);
TELE.addData("Bearing Error", hasValidTarget ? String.format("%.2f", tagBearingDeg) : "NO TARGET"); TELE.addData("LL Valid", result.isValid());
TELE.addData("LL getTx", result.getTx());
TELE.addData("LL Offset", offset);
//TELE.addData("Bearing Error", hasValidTarget ? String.format("%.2f", tagBearingDeg) : "NO TARGET");
TELE.addData("Learned Offset", "%.2f", offset); TELE.addData("Learned Offset", "%.2f", offset);
} }