34 Commits

Author SHA1 Message Date
d1434fbaa8 Add Targeting values from shootertesting. Tune flywheel with shootertest. Add additional telemetry. 2026-01-26 01:00:03 -06:00
d216ce78fc Improve Spindexer shaking. Upgrade shooterTest to control the spindexer and fix flywheel real time pidf coef updates.. 2026-01-25 16:48:27 -06:00
8dc03adfd3 Merge with LimelightTesting. 2026-01-25 11:39:26 -06:00
7ffc51f60a Add shoot all ball order 2026-01-25 11:33:56 -06:00
7625f9a640 stash 2026-01-24 17:53:02 -06:00
fefeeb1f2e i need you @KeshavAnandCode 2026-01-24 17:18:57 -06:00
b5a31afe52 i need you @KeshavAnandCode 2026-01-24 15:42:32 -06:00
8d29a80696 need to add zero code to properly test 2026-01-24 14:45:35 -06:00
5922f4e935 need to add zero code to properly test 2026-01-23 22:50:33 -06:00
78d38481a7 stash 2026-01-23 21:44:29 -06:00
8a4bfecbf8 turret 2026-01-23 21:24:38 -06:00
3591e20001 Merge branch 'Targeting' 2026-01-23 20:24:16 -06:00
4050a354f7 Update TelopV3 and Targeting for merge conflicts. 2026-01-23 20:19:21 -06:00
16ffdd003f stash 2026-01-23 19:38:47 -06:00
f20e640c62 Merge remote-tracking branch 'origin/master' into Targeting
# Conflicts:
#	TeamCode/src/main/java/org/firstinspires/ftc/teamcode/teleop/TeleopV3.java
#	TeamCode/src/main/java/org/firstinspires/ftc/teamcode/tests/TurretTest.java
#	TeamCode/src/main/java/org/firstinspires/ftc/teamcode/utils/Turret.java
2026-01-22 22:13:07 -06:00
c2e9d8fa87 Merge remote-tracking branch 'origin/Targeting' into Targeting 2026-01-22 22:00:41 -06:00
46a5366a4a Add Auto ball detect on startup to spindexer to detect how many balls are already in spindexer on power on. 2026-01-22 21:59:58 -06:00
fbdeb6e291 Turret works y8ippee horray hurrah ig 2026-01-22 21:04:25 -06:00
abhiram vishnubhotla
298b7bca8c Merge pull request #13 from Technical-Turbulence-FTC/feature/interpolation
Feature/interpolation
2026-01-22 20:21:05 -06:00
2f0fcad128 updated interpolation in teleop 2026-01-22 20:06:08 -06:00
45199b952b added interpolation 2026-01-22 20:03:00 -06:00
76ceb91fb7 Merge branch 'Targeting' of https://github.com/Technical-Turbulence-FTC/DecodeFTCMain into turret-refactor-updates 2026-01-22 19:28:42 -06:00
daccec4fdd Add Targeting Class with initial values that still need tuning. Connect Targeting Class to TeleOpV3. Clean up unused code in Flywheel class. 2026-01-22 00:00:17 -06:00
b55d44ae97 Merge branch 'Targeting' of https://github.com/Technical-Turbulence-FTC/DecodeFTCMain into turret-refactor-updates 2026-01-21 20:01:22 -06:00
50212015e3 trackGoal expected robot centric view, but was fed a field centric view. simple trig to use a deltaPos instead of just pos 2026-01-21 19:04:30 -06:00
c271c88e45 Merge branch 'master' of https://github.com/Technical-Turbulence-FTC/DecodeFTCMain into test/continuous_ll_track 2026-01-21 18:36:06 -06:00
33300876ef Merge remote-tracking branch 'origin/master' into Targeting 2026-01-21 09:28:59 -06:00
e1745500cc Create new targeting class. Fix Flywheel Error with motor2 velocity and include spindexer pos updates. 2026-01-21 09:28:21 -06:00
0dbf155608 stash 2026-01-20 21:18:42 -06:00
313eeeaa95 Merge remote-tracking branch 'origin/SpindexerPosUpdate' 2026-01-20 20:59:56 -06:00
b28647373a no errors 2026-01-20 20:57:14 -06:00
7e7254aaea turret refaftoring 2026-01-20 20:52:23 -06:00
a3068cea2e Merge branch 'SpindexerRefactor' of https://github.com/Technical-Turbulence-FTC/DecodeFTCMain into test/continuous_ll_track 2026-01-20 19:17:16 -06:00
f1d4bb9d24 continous ll tracking, TEST 2026-01-19 10:38:34 -06:00
19 changed files with 819 additions and 258 deletions

View File

@@ -710,7 +710,7 @@ public class ProtoAutoClose_V3 extends LinearOpMode {
bearing = result.getTx();
}
}
double turretPos = robot.turr1Pos.getCurrentPosition() - (bearing / 1300);
double turretPos = (bearing / 1300);
robot.turr1.setPosition(turretPos);
robot.turr2.setPosition(1 - turretPos);
}

View File

@@ -12,7 +12,7 @@ public class Poses {
public static double relativeGoalHeight = goalHeight - turretHeight;
public static Pose2d goalPose = new Pose2d(-15, 0, 0);
public static Pose2d goalPose = new Pose2d(-10, 0, 0);
public static double rx1 = 40, ry1 = -7, rh1 = 0;
public static double rx2a = 41, ry2a = 18, rh2a = Math.toRadians(140);
@@ -38,6 +38,6 @@ public class Poses {
public static double bx4b = 48, by4b = -79, bh4b = Math.toRadians(-140);
public static double rfx1 = 10, rfy1 = 0, rfh1 = 0; //TODO: test this
public static Pose2d teleStart = new Pose2d(rx1, ry1, rh1);
public static Pose2d teleStart = new Pose2d(0, 0, 0);
}

View File

@@ -5,11 +5,11 @@ import com.acmerobotics.dashboard.config.Config;
@Config
public class ServoPositions {
public static double spindexer_intakePos1 = 0.19;
public static double spindexer_intakePos1 = 0.18;
public static double spindexer_intakePos2 = 0.35;//0.5;
public static double spindexer_intakePos2 = 0.36;//0.5;
public static double spindexer_intakePos3 = 0.51;//0.66;
public static double spindexer_intakePos3 = 0.54;//0.66;
public static double spindexer_outtakeBall3 = 0.47;
@@ -42,4 +42,8 @@ public class ServoPositions {
public static double turret_detectBlueClose = 0.6;
public static double turrDefault = 0.4;
public static double turrMin = 0.2;
public static double turrMax = 0.8;
}

View File

@@ -21,4 +21,6 @@ public class ShooterVars {
// VELOCITY CONSTANTS
public static int AUTO_CLOSE_VEL = 3175; //3300;
public static int AUTO_FAR_VEL = 4000; //TODO: test this
public static Types.Motif currentMotif = Types.Motif.NONE;
}

View File

@@ -0,0 +1,10 @@
package org.firstinspires.ftc.teamcode.constants;
public class Types {
public enum Motif {
NONE,
GPP, // Green, Purple, Purple
PGP, // Purple, Green, Purple
PPG // Purple, Purple, Green
}
}

View File

@@ -1,4 +0,0 @@
package org.firstinspires.ftc.teamcode.constants;
public class blank {
}

View File

@@ -1,11 +1,9 @@
package org.firstinspires.ftc.teamcode.teleop;
import static org.firstinspires.ftc.teamcode.constants.Color.redAlliance;
import static org.firstinspires.ftc.teamcode.constants.Poses.teleStart;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.spindexer_intakePos1;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.transferServo_in;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.transferServo_out;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.turrDefault;
import static org.firstinspires.ftc.teamcode.utils.Servos.spinD;
import static org.firstinspires.ftc.teamcode.utils.Servos.spinF;
import static org.firstinspires.ftc.teamcode.utils.Servos.spinI;
@@ -21,7 +19,7 @@ import com.acmerobotics.roadrunner.TranslationalVelConstraint;
import com.acmerobotics.roadrunner.Vector2d;
import com.acmerobotics.roadrunner.ftc.Actions;
import com.arcrobotics.ftclib.controller.PIDFController;
import com.qualcomm.hardware.limelightvision.LLResult;
import com.qualcomm.hardware.limelightvision.Limelight3A;
import com.qualcomm.hardware.lynx.LynxModule;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
@@ -33,6 +31,8 @@ import org.firstinspires.ftc.teamcode.utils.Flywheel;
import org.firstinspires.ftc.teamcode.utils.Robot;
import org.firstinspires.ftc.teamcode.utils.Servos;
import org.firstinspires.ftc.teamcode.utils.Spindexer;
import org.firstinspires.ftc.teamcode.utils.Targeting;
import org.firstinspires.ftc.teamcode.utils.Turret;
import java.util.ArrayList;
import java.util.List;
@@ -53,6 +53,8 @@ public class TeleopV3 extends LinearOpMode {
public static boolean manualTurret = true;
public double vel = 3000;
public boolean autoVel = true;
public boolean targetingVel = true;
public boolean targetingHood = true;
public double manualOffset = 0.0;
public boolean autoHood = true;
public boolean green1 = false;
@@ -72,6 +74,8 @@ public class TeleopV3 extends LinearOpMode {
Flywheel flywheel;
MecanumDrive drive;
Spindexer spindexer;
Targeting targeting;
Targeting.Settings targetingSettings;
double autoHoodOffset = 0.0;
int shooterTicker = 0;
@@ -116,6 +120,7 @@ public class TeleopV3 extends LinearOpMode {
private double transferStamp = 0.0;
private int tickerA = 1;
private boolean transferIn = false;
boolean turretInterpolate = false;
public static double velPrediction(double distance) {
if (distance < 30) {
@@ -146,18 +151,23 @@ public class TeleopV3 extends LinearOpMode {
flywheel = new Flywheel(hardwareMap);
drive = new MecanumDrive(hardwareMap, teleStart);
spindexer = new Spindexer(hardwareMap);
targeting = new Targeting();
targetingSettings = new Targeting.Settings(0.0, 0.0);
PIDFController tController = new PIDFController(tp, ti, td, tf);
tController.setTolerance(0.001);
//
// if (redAlliance) {
// robot.limelight.pipelineSwitch(3);
// } else {
// robot.limelight.pipelineSwitch(2);
// }
if (redAlliance) {
robot.limelight.pipelineSwitch(3);
} else {
robot.limelight.pipelineSwitch(2);
}
// robot.limelight.start();
robot.limelight.start();
Turret turret = new Turret(robot, TELE, robot.limelight);
waitForStart();
waitForStart();
if (isStopRequested()) return;
@@ -372,43 +382,24 @@ public class TeleopV3 extends LinearOpMode {
double robotY = robY - yOffset;
double robotHeading = drive.localizer.getPose().heading.toDouble();
double goalX = -10;
double goalX = -15;
double goalY = 0;
double dx = goalX - robotX; // delta x from robot to goal
double dy = goalY - robotY; // delta y from robot to goal
double dx = robotX - goalX; // delta x from robot to goal
double dy = robotY - goalY; // delta y from robot to goal
Pose2d deltaPose = new Pose2d(dx, dy, robotHeading);
double distanceToGoal = Math.sqrt(dx * dx + dy * dy);
desiredTurretAngle = (Math.toDegrees(Math.atan2(dy, dx)) + 360) % 360;
targetingSettings = targeting.calculateSettings
(robotX,robotY,robotHeading,0.0, turretInterpolate);
desiredTurretAngle += manualOffset + error;
offset = desiredTurretAngle - 180 - (Math.toDegrees(robotHeading - headingOffset));
if (offset > 135) {
offset -= 360;
}
double pos = turrDefault;
TELE.addData("offset", offset);
pos -= offset * ((double) 1 / 360);
if (pos < 0.13) {
pos = 0.13;
} else if (pos > 0.83) {
pos = 0.83;
}
//SHOOTER:
flywheel.manageFlywheel(vel);
turret.trackGoal(deltaPose);
//VELOCITY AUTOMATIC
if (autoVel) {
if (targetingVel) {
vel = targetingSettings.flywheelRPM;
} else if (autoVel) {
vel = velPrediction(distanceToGoal);
} else {
vel = manualVel;
@@ -430,57 +421,39 @@ public class TeleopV3 extends LinearOpMode {
manualVel = 3100;
}
//SHOOTER:
flywheel.manageFlywheel(vel);
//TODO: test the camera teleop code
TELE.addData("posS2", pos);
if (y < 0.3 && y > -0.3 && x < 0.3 && x > -0.3 && rx < 0.3 && rx > -0.3) { //not moving
double bearing;
LLResult result = robot.limelight.getLatestResult();
if (result != null) {
if (result.isValid()) {
bearing = result.getTx() * bMult;
double bearingCorrection = bearing / bDiv;
error += bearingCorrection;
camTicker++;
TELE.addData("tx", bearingCorrection);
TELE.addData("ty", result.getTy());
}
}
} else {
camTicker = 0;
overrideTurr = false;
}
if (!overrideTurr) {
turretPos = pos;
}
TELE.addData("posS3", turretPos);
if (manualTurret) {
pos = turrDefault + (manualOffset / 100) + error;
}
if (!overrideTurr) {
turretPos = pos;
}
if (Math.abs(gamepad2.left_stick_x)>0.2) {
manualOffset += 1.35 * gamepad2.left_stick_x;
}
robot.turr1.setPosition(pos);
robot.turr2.setPosition(1 - pos);
// if (y < 0.3 && y > -0.3 && x < 0.3 && x > -0.3 && rx < 0.3 && rx > -0.3) { //not moving
// double bearing;
//
// LLResult result = robot.light.getLatestResult();
// if (result != null) {
// if (result.isValid()) {
// bearing = result.getTx() * bMult;
//
// double bearingCorrection = bearing / bDiv;
//
// error += bearingCorrection;
//
// camTicker++;
// TELE.addData("tx", bearingCorrection);
// TELE.addData("ty", result.getTy());
// }
// }
//
// } else {
// camTicker = 0;
// overrideTurr = false;
// }
//HOOD:
if (autoHood) {
if (targetingHood) {
robot.hood.setPosition(targetingSettings.hoodAngle);
} else if (autoHood) {
robot.hood.setPosition(0.15 + hoodOffset);
} else {
robot.hood.setPosition(hoodDefaultPos + hoodOffset);
@@ -659,7 +632,6 @@ public class TeleopV3 extends LinearOpMode {
}
}
//
// if (shootAll) {
//
@@ -842,19 +814,33 @@ public class TeleopV3 extends LinearOpMode {
TELE.addData("hood", robot.hood.getPosition());
TELE.addData("targetVel", vel);
TELE.addData("Velocity", flywheel.getVelo());
TELE.addData("Velo1", flywheel.velo1);
TELE.addData("Velo2", flywheel.velo2);
TELE.addData("shootOrder", shootOrder);
TELE.addData("oddColor", oddBallColor);
// Spindexer Debug
TELE.addData("spinEqual", servo.spinEqual(spindexer_intakePos1));
TELE.addData("spinCommmandedPos", spindexer.commandedIntakePosition);
TELE.addData("spinIntakeState", spindexer.currentIntakeState);
TELE.addData("spinTestCounter", spindexer.counter);
TELE.addData("autoSpintake", autoSpintake);
TELE.addData("distanceRearCenter", spindexer.distanceRearCenter);
TELE.addData("distanceFrontDriver", spindexer.distanceFrontDriver);
TELE.addData("distanceFrontPassenger", spindexer.distanceFrontPassenger);
//TELE.addData("distanceRearCenter", spindexer.distanceRearCenter);
//TELE.addData("distanceFrontDriver", spindexer.distanceFrontDriver);
//TELE.addData("distanceFrontPassenger", spindexer.distanceFrontPassenger);
TELE.addData("shootall commanded", shootAll);
// Targeting Debug
TELE.addData("robotX", robotX);
TELE.addData("robotY", robotY);
TELE.addData("robotInchesX", targeting.robotInchesX);
TELE.addData( "robotInchesY", targeting.robotInchesY);
TELE.addData("Targeting Interpolate", turretInterpolate);
TELE.addData("Targeting GridX", targeting.robotGridX);
TELE.addData("Targeting GridY", targeting.robotGridY);
TELE.addData("Targeting FlyWheel", targetingSettings.flywheelRPM);
TELE.addData("Targeting HoodAngle", targetingSettings.hoodAngle);
TELE.addData("timeSinceStamp", getRuntime() - shootStamp);
TELE.update();
ticker++;

View File

@@ -4,32 +4,35 @@ import com.acmerobotics.dashboard.FtcDashboard;
import com.acmerobotics.dashboard.config.Config;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.qualcomm.hardware.limelightvision.LLResult;
import com.qualcomm.hardware.limelightvision.LLResultTypes;
import com.qualcomm.hardware.limelightvision.Limelight3A;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
import java.util.List;
import org.firstinspires.ftc.teamcode.utils.Robot;
import org.firstinspires.ftc.teamcode.utils.Turret;
@Config
@TeleOp
//TODO: fix to get the apriltag that it is reading
public class LimelightTest extends LinearOpMode {
MultipleTelemetry TELE;
Turret turret;
Robot robot;
public static int pipeline = 0; //0 is for test; 1 for obelisk; 2 is for blue track; 3 is for red track
public static int mode = 0; //0 for bare testing, 1 for obelisk, 2 for blue track, 3 for red track
public static boolean turretMode = false;
public static double turretPos = 0.501;
@Override
public void runOpMode() throws InterruptedException {
Limelight3A limelight = hardwareMap.get(Limelight3A.class, "limelight");
TELE = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
limelight.pipelineSwitch(pipeline);
robot = new Robot(hardwareMap);
turret = new Turret(robot, TELE, robot.limelight);
robot.limelight.pipelineSwitch(pipeline);
waitForStart();
if (isStopRequested()) return;
limelight.start();
while (opModeIsActive()){
if (mode == 0){
limelight.pipelineSwitch(pipeline);
LLResult result = limelight.getLatestResult();
robot.limelight.pipelineSwitch(pipeline);
LLResult result = robot.limelight.getLatestResult();
if (result != null) {
if (result.isValid()) {
TELE.addData("tx", result.getTx());
@@ -38,40 +41,29 @@ public class LimelightTest extends LinearOpMode {
}
}
} else if (mode == 1){
limelight.pipelineSwitch(1);
LLResult result = limelight.getLatestResult();
if (result != null && result.isValid()) {
List<LLResultTypes.FiducialResult> fiducials = result.getFiducialResults();
for (LLResultTypes.FiducialResult fiducial : fiducials) {
int id = fiducial.getFiducialId();
TELE.addData("ID", id);
TELE.update();
}
}
} else if (mode == 2){
limelight.pipelineSwitch(4);
LLResult result = limelight.getLatestResult();
if (result != null) {
if (result.isValid()) {
TELE.addData("tx", result.getTx());
TELE.addData("ty", result.getTy());
TELE.update();
}
}
} else if (mode == 3){
limelight.pipelineSwitch(5);
LLResult result = limelight.getLatestResult();
if (result != null) {
if (result.isValid()) {
TELE.addData("tx", result.getTx());
TELE.addData("ty", result.getTy());
TELE.update();
}
}
int obeliskID = turret.detectObelisk();
TELE.addData("Limelight ID", obeliskID);
TELE.update();
} else if (mode == 2 || mode == 3){ // Use redAlliance variable to switch between red and blue
double tx = turret.getBearing();
double ty = turret.getTy();
double x = turret.getLimelightX();
double y = turret.getLimelightY();
TELE.addData("tx", tx);
TELE.addData("ty", ty);
TELE.addData("x", x);
TELE.addData("y", y);
TELE.update();
} else {
limelight.pipelineSwitch(0);
robot.limelight.pipelineSwitch(0);
}
if (turretMode){
if (turretPos != 0.501){
turret.manualSetTurret(turretPos);
}
}
}
}
}

View File

@@ -52,7 +52,6 @@ public class PIDServoTest extends LinearOpMode {
}
telemetry.addData("pos", pos);
telemetry.addData("Turret Voltage", robot.turr1Pos.getCurrentPosition());
telemetry.addData("Spindex Voltage", robot.spin1Pos.getVoltage());
telemetry.addData("target", target);
telemetry.addData("Mode", mode);

View File

@@ -1,6 +1,8 @@
package org.firstinspires.ftc.teamcode.tests;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.*;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.spindexer_intakePos1;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.transferServo_in;
import static org.firstinspires.ftc.teamcode.constants.ServoPositions.transferServo_out;
import com.acmerobotics.dashboard.FtcDashboard;
import com.acmerobotics.dashboard.config.Config;
@@ -11,28 +13,40 @@ import com.qualcomm.robotcore.hardware.DcMotorEx;
import org.firstinspires.ftc.teamcode.utils.Flywheel;
import org.firstinspires.ftc.teamcode.utils.Robot;
import org.firstinspires.ftc.teamcode.utils.Spindexer;
@Config
@TeleOp
public class ShooterTest extends LinearOpMode {
public static int mode = 0;
public static int mode = 1;
public static double parameter = 0.0;
// --- CONSTANTS YOU TUNE ---
//TODO: @Daniel FIX THE BELOW CONSTANTS A LITTLE IF NEEDED
public static double Velocity = 0.0;
public static double P = 40.0;
public static double I = 0.3;
public static double D = 7.0;
public static double F = 10.0;
public static double P = 255.0;
public static double I = 0.0;
public static double D = 0.0;
public static double F = 7.5;
public static double transferPower = 1.0;
public static double hoodPos = 0.501;
public static double turretPos = 0.501;
public static boolean shoot = false;
public static boolean intake = false;
Robot robot;
Flywheel flywheel;
double shootStamp = 0.0;
boolean shootAll = false;
public double spinPow = 0.09;
public static boolean enableHoodAutoOpen = false;
public double hoodAdjust = 0.0;
public static double hoodAdjustFactor = 1.0;
Spindexer spindexer ;
@Override
public void runOpMode() throws InterruptedException {
@@ -40,6 +54,7 @@ public class ShooterTest extends LinearOpMode {
DcMotorEx leftShooter = robot.shooter1;
DcMotorEx rightShooter = robot.shooter2;
flywheel = new Flywheel(hardwareMap);
spindexer = new Spindexer(hardwareMap);
MultipleTelemetry TELE = new MultipleTelemetry(
telemetry, FtcDashboard.getInstance().getTelemetry()
@@ -55,21 +70,65 @@ public class ShooterTest extends LinearOpMode {
rightShooter.setPower(parameter);
leftShooter.setPower(parameter);
} else if (mode == 1) {
flywheel.setPIDF(P,I,D,F);
flywheel.setPIDF(P, I, D, F);
flywheel.manageFlywheel((int) Velocity);
}
if (hoodPos != 0.501) {
robot.hood.setPosition(hoodPos);
if (enableHoodAutoOpen) {
robot.hood.setPosition(hoodPos+(hoodAdjustFactor*(flywheel.getVelo()/Velocity)));
} else {
robot.hood.setPosition(hoodPos);
}
}
if (intake) {
robot.intake.setPower(1);
robot.transfer.setPower(transferPower);
if (shoot) {
robot.transferServo.setPosition(transferServo_in);
} else {
robot.transferServo.setPosition(transferServo_out);
robot.intake.setPower(0);
}
if (shoot) {
shootStamp = getRuntime();
shootAll = true;
shoot = false;
robot.transfer.setPower(transferPower);
}
if (shootAll) {
//intake = false;
//reject = false;
// TODO: Change starting position based on desired order to shoot green ball
//spindexPos = spindexer_intakePos1;
if (getRuntime() - shootStamp < 3.5) {
robot.transferServo.setPosition(transferServo_in);
robot.spin1.setPower(-spinPow);
robot.spin2.setPower(spinPow);
} else {
robot.transferServo.setPosition(transferServo_out);
//spindexPos = spindexer_intakePos1;
shootAll = false;
robot.transferServo.setPosition(transferServo_out);
robot.transfer.setPower(0);
robot.spin1.setPower(0);
robot.spin2.setPower(0);
spindexer.resetSpindexer();
spindexer.processIntake();
}
} else {
spindexer.processIntake();
}
TELE.addData("Velocity", flywheel.getVelo());
TELE.addData("Velocity 1", flywheel.getVelo1());
TELE.addData("Velocity 2", flywheel.getVelo2());

View File

@@ -0,0 +1,50 @@
package org.firstinspires.ftc.teamcode.tests;
import com.acmerobotics.dashboard.FtcDashboard;
import com.acmerobotics.dashboard.config.Config;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.acmerobotics.roadrunner.Pose2d;
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import org.firstinspires.ftc.teamcode.libs.RR.MecanumDrive;
import org.firstinspires.ftc.teamcode.utils.Robot;
import org.firstinspires.ftc.teamcode.utils.Turret;
@Autonomous
@Config
public class TurretTest extends LinearOpMode {
public static boolean zeroTurr = false;
@Override
public void runOpMode() throws InterruptedException {
Robot robot = new Robot(hardwareMap);
MultipleTelemetry TELE = new MultipleTelemetry(
telemetry, FtcDashboard.getInstance().getTelemetry()
);
Turret turret = new Turret(robot, TELE, robot.limelight);
waitForStart();
MecanumDrive drive = new MecanumDrive(hardwareMap, new Pose2d(15, 0,0));
while(opModeIsActive()){
drive.updatePoseEstimate();
turret.trackGoal(drive.localizer.getPose());
TELE.addData("tpos", turret.getTurrPos());
TELE.addData("Limelight tx", turret.getBearing());
TELE.addData("Limelight ty", turret.getTy());
TELE.addData("Limelight X", turret.getLimelightX());
TELE.addData("Limelight Y", turret.getLimelightY());
if(zeroTurr){
turret.zeroTurretEncoder();
}
TELE.update();
}
}
}

View File

@@ -1,30 +1,25 @@
package org.firstinspires.ftc.teamcode.utils;
import static org.firstinspires.ftc.teamcode.constants.ShooterVars.kP;
import static org.firstinspires.ftc.teamcode.constants.ShooterVars.maxStep;
import com.arcrobotics.ftclib.controller.PIDFController;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.HardwareMap;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.qualcomm.robotcore.hardware.PIDFCoefficients;
public class Flywheel {
Robot robot;
MultipleTelemetry TELE;
double initPos = 0.0;
double stamp = 0.0;
double stamp1 = 0.0;
double ticker = 0.0;
double currentPos = 0.0;
public PIDFCoefficients shooterPIDF1, shooterPIDF2;
double velo = 0.0;
double velo1 = 0.0;
double velo2 = 0.0;
public double velo1 = 0.0;
public double velo2 = 0.0;
double targetVelocity = 0.0;
double powPID = 0.0;
boolean steady = false;
public Flywheel (HardwareMap hardwareMap) {
robot = new Robot(hardwareMap);
shooterPIDF1 = new PIDFCoefficients
(robot.shooterPIDF_P, robot.shooterPIDF_I, robot.shooterPIDF_D, robot.shooterPIDF_F);
shooterPIDF2 = new PIDFCoefficients
(robot.shooterPIDF_P, robot.shooterPIDF_I, robot.shooterPIDF_D, robot.shooterPIDF_F);
}
public double getVelo () {
@@ -43,14 +38,14 @@ public class Flywheel {
// Set the robot PIDF for the next cycle.
public void setPIDF(double p, double i, double d, double f) {
robot.shooterPIDF.p = p;
robot.shooterPIDF.i = i;
robot.shooterPIDF.d = d;
robot.shooterPIDF.f = f;
}
private double getTimeSeconds ()
{
return (double) System.currentTimeMillis()/1000.0;
shooterPIDF1.p = p;
shooterPIDF1.i = i;
shooterPIDF1.d = d;
shooterPIDF1.f = f;
shooterPIDF2.p = p;
shooterPIDF2.i = i;
shooterPIDF2.d = d;
shooterPIDF2.f = f;
}
// Convert from RPM to Ticks per Second
@@ -62,19 +57,16 @@ public class Flywheel {
public double manageFlywheel(double commandedVelocity) {
targetVelocity = commandedVelocity;
// Turn PIDF for Target Velocities
//robot.shooterPIDF.p = P;
//robot.shooterPIDF.i = I;
//robot.shooterPIDF.d = D;
//robot.shooterPIDF.f = F;
robot.shooter1.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, robot.shooterPIDF);
robot.shooter2.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, robot.shooterPIDF);
// Add code here to set PIDF based on desired RPM
robot.shooter1.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, shooterPIDF1);
robot.shooter2.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, shooterPIDF2);
robot.shooter1.setVelocity(RPM_to_TPS(targetVelocity));
robot.shooter2.setVelocity(RPM_to_TPS(targetVelocity));
// Record Current Velocity
velo1 = TPS_to_RPM(robot.shooter1.getVelocity());
velo2 = TPS_to_RPM(robot.shooter1.getVelocity()); // Possible error: should it be shooter2 not shooter1?
velo2 = TPS_to_RPM(robot.shooter2.getVelocity());
velo = Math.max(velo1,velo2);
// really should be a running average of the last 5

View File

@@ -8,6 +8,8 @@ import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection;
@TeleOp
@Config
public class PositionalServoProgrammer extends LinearOpMode {
@@ -25,11 +27,17 @@ public class PositionalServoProgrammer extends LinearOpMode {
public static double hoodPos = 0.501;
public static int mode = 0; //0 for positional, 1 for power
Turret turret;
@Override
public void runOpMode() throws InterruptedException {
robot = new Robot(hardwareMap);
TELE = new MultipleTelemetry(telemetry, FtcDashboard.getInstance().getTelemetry());
servo = new Servos(hardwareMap);
turret = new Turret(robot, TELE, robot.limelight );
waitForStart();
if (isStopRequested()) return;
while (opModeIsActive()){
@@ -66,13 +74,13 @@ public class PositionalServoProgrammer extends LinearOpMode {
//TODO: @KeshavAnandCode do the above please
TELE.addData("spindexer pos", servo.getSpinPos());
TELE.addData("turret pos", servo.getTurrPos());
TELE.addData("turret pos", robot.turr1.getPosition());
TELE.addData("spindexer voltage 1", robot.spin1Pos.getVoltage());
TELE.addData("spindexer voltage 2", robot.spin2Pos.getVoltage());
TELE.addData("hood pos", robot.hood.getPosition());
TELE.addData("transferServo voltage", robot.transferServoPos.getVoltage());
TELE.addData("turret voltage", robot.turr1Pos.getCurrentPosition());
TELE.addData("spindexer pow", robot.spin1.getPower());
TELE.addData("tpos ", turret.getTurrPos() );
TELE.update();
}
}

View File

@@ -1,5 +1,6 @@
package org.firstinspires.ftc.teamcode.utils;
import com.acmerobotics.dashboard.config.Config;
import com.qualcomm.hardware.limelightvision.Limelight3A;
import com.qualcomm.hardware.rev.RevColorSensorV3;
import com.qualcomm.robotcore.hardware.AnalogInput;
@@ -14,10 +15,13 @@ import com.qualcomm.robotcore.hardware.Servo;
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor;
@Config
public class Robot {
//Initialize Public Components
public static boolean usingLimelight = true;
public static boolean usingCamera = false;
public DcMotorEx frontLeft;
public DcMotorEx frontRight;
public DcMotorEx backLeft;
@@ -25,12 +29,11 @@ public class Robot {
public DcMotorEx intake;
public DcMotorEx transfer;
public PIDFCoefficients shooterPIDF;
public double shooterPIDF_P = 10.0;
public double shooterPIDF_I = 0.6;
public double shooterPIDF_D = 5.0;
public double shooterPIDF_F = 10.0;
public double[] shooterPIDF_StepSizes = {10.0,1.0,0.001, 0.0001};
public double shooterPIDF_P = 255.0;
public double shooterPIDF_I = 0.0;
public double shooterPIDF_D = 0.0;
public double shooterPIDF_F = 7.5;
public double[] shooterPIDF_StepSizes = {10.0, 1.0, 0.001, 0.0001};
public DcMotorEx shooter1;
public DcMotorEx shooter2;
public Servo hood;
@@ -41,7 +44,7 @@ public class Robot {
public CRServo spin2;
public AnalogInput spin1Pos;
public AnalogInput spin2Pos;
public DcMotorEx turr1Pos;
public AnalogInput turr1Pos;
public AnalogInput transferServoPos;
public AprilTagProcessor aprilTagProcessor;
public WebcamName webcam;
@@ -50,10 +53,6 @@ public class Robot {
public RevColorSensorV3 color3;
public Limelight3A limelight;
public static boolean usingLimelight = true;
public static boolean usingCamera = true;
public Robot(HardwareMap hardwareMap) {
//Define components w/ hardware map
@@ -77,11 +76,13 @@ public class Robot {
shooter2 = hardwareMap.get(DcMotorEx.class, "shooter2");
//TODO: figure out which shooter motor is reversed using ShooterTest and change it in code @KeshavAnandCode
shooter1.setDirection(DcMotorSimple.Direction.REVERSE);
shooterPIDF = new PIDFCoefficients(shooterPIDF_P,shooterPIDF_I , shooterPIDF_D, shooterPIDF_F);
shooterPIDF = new PIDFCoefficients(shooterPIDF_P, shooterPIDF_I, shooterPIDF_D, shooterPIDF_F);
shooter1.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
shooter1.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, shooterPIDF);
shooter1.setVelocity(0);
shooter2.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
shooter2.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, shooterPIDF);
shooter2.setVelocity(0);
hood = hardwareMap.get(Servo.class, "hood");
@@ -89,7 +90,7 @@ public class Robot {
turr2 = hardwareMap.get(Servo.class, "t2");
turr1Pos = intake; // Encoder of turret plugged in intake port
turr1Pos = hardwareMap.get(AnalogInput.class, "t1Pos"); // Encoder of turret plugged in intake port
//TODO: check spindexer configuration (both servo and analog input) - check comments in PositionalServoProgrammer
spin1 = hardwareMap.get(CRServo.class, "spin1");
@@ -118,9 +119,9 @@ public class Robot {
color3 = hardwareMap.get(RevColorSensorV3.class, "c3");
if (usingLimelight){
if (usingLimelight) {
limelight = hardwareMap.get(Limelight3A.class, "limelight");
} else if (usingCamera){
} else if (usingCamera) {
webcam = hardwareMap.get(WebcamName.class, "Webcam 1");
aprilTagProcessor = AprilTagProcessor.easyCreateWithDefaults();
}

View File

@@ -8,7 +8,7 @@ import com.qualcomm.robotcore.hardware.HardwareMap;
public class Servos {
//PID constants
// TODO: get PIDF constants
public static double spinP = 3.3, spinI = 0, spinD = 0.1, spinF = 0.02;
public static double spinP = 2.0, spinI = 0, spinD = 0.3, spinF = 0.02;
public static double turrP = 1.1, turrI = 0.25, turrD = 0.0625, turrF = 0;
public static double spin_scalar = 1.0086;
public static double spin_restPos = 0.0;
@@ -40,21 +40,19 @@ public class Servos {
}
public boolean spinEqual(double pos) {
return Math.abs(pos - this.getSpinPos()) < 0.02;
return Math.abs(pos - this.getSpinPos()) < 0.03;
}
public double getTurrPos() {
return (double) ((double) robot.turr1Pos.getCurrentPosition() / 1024.0) * ((double) 44.0 / (double) 77.0);
return 1.0;
}
public double setTurrPos(double pos) {
turretPID.setPIDF(turrP, turrI, turrD, turrF);
return spinPID.calculate(this.getTurrPos(), pos);
return 1.0;
}
public boolean turretEqual(double pos) {
return Math.abs(pos - this.getTurrPos()) < 0.01;
return true;
}
}

View File

@@ -16,9 +16,11 @@ import static org.firstinspires.ftc.teamcode.utils.Servos.spinI;
import static org.firstinspires.ftc.teamcode.utils.Servos.spinP;
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
import org.firstinspires.ftc.teamcode.constants.Types;
import org.firstinspires.ftc.teamcode.libs.RR.MecanumDrive;
public class Spindexer {
Robot robot;
Servos servos;
Flywheel flywheel;
@@ -36,6 +38,7 @@ public class Spindexer {
public double distanceFrontDriver = 0.0;
public double distanceFrontPassenger = 0.0;
public Types.Motif desiredMotif = Types.Motif.NONE;
// For Use
enum RotatedBallPositionNames {
REARCENTER,
@@ -52,7 +55,9 @@ public class Spindexer {
}
enum IntakeState {
UNKNOWN,
UNKNOWN_START,
UNKNOWN_MOVE,
UNKNOWN_DETECT,
INTAKE,
FINDNEXT,
MOVING,
@@ -60,10 +65,12 @@ public class Spindexer {
SHOOTNEXT,
SHOOTMOVING,
SHOOTWAIT,
SHOOT_ALL_PREP,
SHOOT_ALL_READY
};
public IntakeState currentIntakeState = IntakeState.UNKNOWN;
public IntakeState currentIntakeState = IntakeState.UNKNOWN_START;
public int unknownColorDetect = 0;
enum BallColor {
UNKNOWN,
GREEN,
@@ -131,13 +138,13 @@ public class Spindexer {
for (int i = 0; i < 3; i++) {
resetBallPosition(i);
}
currentIntakeState = IntakeState.UNKNOWN;
currentIntakeState = IntakeState.UNKNOWN_START;
}
// Detects if a ball is found and what color.
// Returns true is there was a new ball found in Position 1
// FIXIT: Reduce number of times that we read the color sensors for loop times.
public boolean detectBalls() {
public boolean detectBalls(boolean detectRearColor, boolean detectFrontColor) {
boolean newPos1Detection = false;
int spindexerBallPos = 0;
@@ -153,18 +160,20 @@ public class Spindexer {
// Mark Ball Found
newPos1Detection = true;
// Detect which color
double green = robot.color1.getNormalizedColors().green;
double red = robot.color1.getNormalizedColors().red;
double blue = robot.color1.getNormalizedColors().blue;
if (detectRearColor) {
// Detect which color
double green = robot.color1.getNormalizedColors().green;
double red = robot.color1.getNormalizedColors().red;
double blue = robot.color1.getNormalizedColors().blue;
double gP = green / (green + red + blue);
double gP = green / (green + red + blue);
// FIXIT - Add filtering to improve accuracy.
if (gP >= 0.4) {
ballPositions[commandedIntakePosition].ballColor = BallColor.PURPLE; // purple
} else {
ballPositions[commandedIntakePosition].ballColor = BallColor.GREEN; // purple
// FIXIT - Add filtering to improve accuracy.
if (gP >= 0.4) {
ballPositions[commandedIntakePosition].ballColor = BallColor.PURPLE; // purple
} else {
ballPositions[commandedIntakePosition].ballColor = BallColor.GREEN; // purple
}
}
}
// Position 2
@@ -173,18 +182,19 @@ public class Spindexer {
if (distanceFrontDriver < 60) {
// reset FoundEmpty because looking for 3 in a row before reset
ballPositions[spindexerBallPos].foundEmpty = 0;
// FIXIT: Comment out for now due to loop time concerns
// double green = robot.color2.getNormalizedColors().green;
// double red = robot.color2.getNormalizedColors().red;
// double blue = robot.color2.getNormalizedColors().blue;
//
// double gP = green / (green + red + blue);
if (detectFrontColor) {
double green = robot.color2.getNormalizedColors().green;
double red = robot.color2.getNormalizedColors().red;
double blue = robot.color2.getNormalizedColors().blue;
// if (gP >= 0.4) {
// b2 = 2; // purple
// } else {
// b2 = 1; // green
// }
double gP = green / (green + red + blue);
if (gP >= 0.4) {
ballPositions[spindexerBallPos].ballColor = BallColor.PURPLE; // purple
} else {
ballPositions[spindexerBallPos].ballColor = BallColor.GREEN; // purple
}
}
} else {
if (!ballPositions[spindexerBallPos].isEmpty) {
if (ballPositions[spindexerBallPos].foundEmpty > 3) {
@@ -201,18 +211,19 @@ public class Spindexer {
// reset FoundEmpty because looking for 3 in a row before reset
ballPositions[spindexerBallPos].foundEmpty = 0;
// FIXIT: Comment out for now due to loop time concerns
// double green = robot.color3.getNormalizedColors().green;
// double red = robot.color3.getNormalizedColors().red;
// double blue = robot.color3.getNormalizedColors().blue;
if (detectFrontColor) {
double green = robot.color3.getNormalizedColors().green;
double red = robot.color3.getNormalizedColors().red;
double blue = robot.color3.getNormalizedColors().blue;
// double gP = green / (green + red + blue);
double gP = green / (green + red + blue);
// if (gP >= 0.4) {
// b3 = 2; // purple
// } else {
// b3 = 1; // green
// }
if (gP >= 0.4) {
ballPositions[spindexerBallPos].ballColor = BallColor.PURPLE; // purple
} else {
ballPositions[spindexerBallPos].ballColor = BallColor.GREEN; // purple
}
}
} else {
if (!ballPositions[spindexerBallPos].isEmpty) {
if (ballPositions[spindexerBallPos].foundEmpty > 3) {
@@ -255,15 +266,34 @@ public class Spindexer {
public boolean processIntake() {
switch (currentIntakeState) {
case UNKNOWN:
case UNKNOWN_START:
// For now just set position ONE if UNKNOWN
commandedIntakePosition = 0;
servos.setSpinPos(intakePositions[0]);
currentIntakeState = Spindexer.IntakeState.MOVING;
currentIntakeState = Spindexer.IntakeState.UNKNOWN_MOVE;
break;
case UNKNOWN_MOVE:
// Stopping when we get to the new position
if (servos.spinEqual(intakePositions[commandedIntakePosition])) {
currentIntakeState = Spindexer.IntakeState.UNKNOWN_DETECT;
stopSpindexer();
unknownColorDetect = 0;
} else {
// Keep moving the spindexer
moveSpindexerToPos(intakePositions[commandedIntakePosition]);
}
break;
case UNKNOWN_DETECT:
if (unknownColorDetect >5) {
currentIntakeState = Spindexer.IntakeState.FINDNEXT;
} else {
//detectBalls(true, true);
unknownColorDetect++;
}
break;
case INTAKE:
// Ready for intake and Detecting a New Ball
if (detectBalls()) {
if (detectBalls(true, false)) {
ballPositions[commandedIntakePosition].isEmpty = false;
currentIntakeState = Spindexer.IntakeState.FINDNEXT;
} else {
@@ -301,6 +331,7 @@ public class Spindexer {
}
if (currentIntakeState != Spindexer.IntakeState.MOVING) {
// Full
commandedIntakePosition = bestFitMotif();
currentIntakeState = Spindexer.IntakeState.FULL;
}
moveSpindexerToPos(intakePositions[commandedIntakePosition]);
@@ -311,7 +342,7 @@ public class Spindexer {
if (servos.spinEqual(intakePositions[commandedIntakePosition])) {
currentIntakeState = Spindexer.IntakeState.INTAKE;
stopSpindexer();
detectBalls();
//detectBalls(false, false);
} else {
// Keep moving the spindexer
moveSpindexerToPos(intakePositions[commandedIntakePosition]);
@@ -320,7 +351,7 @@ public class Spindexer {
case FULL:
// Double Check Colors
detectBalls();
detectBalls(false, false); // Minimize hardware calls
if (ballPositions[0].isEmpty || ballPositions[1].isEmpty || ballPositions[2].isEmpty) {
// Error handling found an empty spot, get it ready for a ball
currentIntakeState = Spindexer.IntakeState.FINDNEXT;
@@ -329,6 +360,28 @@ public class Spindexer {
moveSpindexerToPos(intakePositions[commandedIntakePosition]);
break;
case SHOOT_ALL_PREP:
// We get here with function call to prepareToShootMotif
// Stopping when we get to the new position
if (servos.spinEqual(intakePositions[commandedIntakePosition])) {
currentIntakeState = Spindexer.IntakeState.SHOOT_ALL_READY;
} else {
// Keep moving the spindexer
moveSpindexerToPos(intakePositions[commandedIntakePosition]); // Possible error: should it be using "outakePositions" instead of "intakePositions"
}
break;
case SHOOT_ALL_READY:
// Double Check Colors
//detectBalls(false, false); // Minimize hardware calls
if (ballPositions[0].isEmpty && ballPositions[1].isEmpty && ballPositions[2].isEmpty) {
// All ball shot move to intake state
currentIntakeState = Spindexer.IntakeState.FINDNEXT;
}
// Maintain Position
moveSpindexerToPos(intakePositions[commandedIntakePosition]);
break;
case SHOOTNEXT:
// Find Next Open Position and start movement
if (!ballPositions[0].isEmpty) {
@@ -357,16 +410,6 @@ public class Spindexer {
// Stopping when we get to the new position
if (servos.spinEqual(outakePositions[commandedIntakePosition])) {
currentIntakeState = Spindexer.IntakeState.SHOOTWAIT;
ballPositions[commandedIntakePosition].isEmpty = true;
// Advance to next full position and wait
// commandedIntakePosition++;
// if (commandedIntakePosition > 2) {
// commandedIntakePosition = 0;
// }
// // Continue moving to next position
// servos.setSpinPos(intakePositions[commandedIntakePosition]);
// currentIntakeState = Spindexer.IntakeState.MOVING;
} else {
// Keep moving the spindexer
moveSpindexerToPos(intakePositions[commandedIntakePosition]); // Possible error: should it be using "outakePositions" instead of "intakePositions"
@@ -378,7 +421,7 @@ public class Spindexer {
if (servos.spinEqual(intakePositions[commandedIntakePosition])) {
currentIntakeState = Spindexer.IntakeState.INTAKE;
stopSpindexer();
detectBalls();
//detectBalls(true, false);
} else {
// Keep moving the spindexer
moveSpindexerToPos(intakePositions[commandedIntakePosition]);
@@ -394,6 +437,55 @@ public class Spindexer {
return false;
}
public void setDesiredMotif (Types.Motif newMotif) {
desiredMotif = newMotif;
}
// Returns the best fit for the motiff
public int bestFitMotif () {
switch (desiredMotif) {
case GPP:
if (ballPositions[0].ballColor == BallColor.GREEN) {
return 2;
} else if (ballPositions[1].ballColor == BallColor.GREEN) {
return 0;
} else {
return 1;
}
//break;
case PGP:
if (ballPositions[0].ballColor == BallColor.GREEN) {
return 0;
} else if (ballPositions[1].ballColor == BallColor.GREEN) {
return 1;
} else {
return 3;
}
//break;
case PPG:
if (ballPositions[0].ballColor == BallColor.GREEN) {
return 1;
} else if (ballPositions[1].ballColor == BallColor.GREEN) {
return 0;
} else {
return 2;
}
//break;
case NONE:
return 0;
//break;
}
return 0;
}
void prepareToShootMotif () {
commandedIntakePosition = bestFitMotif();
}
void shootAllToIntake () {
currentIntakeState = Spindexer.IntakeState.FINDNEXT;
}
public void update()
{
}

View File

@@ -0,0 +1,141 @@
package org.firstinspires.ftc.teamcode.utils;
import android.provider.Settings;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.HardwareMap;
public class Targeting {
MultipleTelemetry TELE;
double cancelOffsetX = 0.0; // was -40.0
double cancelOffsetY = 0.0; // was 7.0
double unitConversionFactor = 0.95;
int tileSize = 24; //inches
public double robotInchesX, robotInchesY = 0.0;
public int robotGridX, robotGridY = 0;
public static class Settings {
public double flywheelRPM = 0.0;
public double hoodAngle = 0.0;
public Settings (double flywheelRPM, double hoodAngle) {
this.flywheelRPM = flywheelRPM;
this.hoodAngle = hoodAngle;
}
}
// Known settings discovered using shooter test.
// Keep the fidelity at 1 floor tile for now but we could also half it if more
// accuracy is needed.
public static final Settings[][] KNOWNTARGETING;
static {
KNOWNTARGETING = new Settings[6][6];
// ROW 0 - Closet to the goals
KNOWNTARGETING[0][0] = new Settings (2300.0, 0.93);
KNOWNTARGETING[0][1] = new Settings (2300.0, 0.93);
KNOWNTARGETING[0][2] = new Settings (2500.0, 0.78);
KNOWNTARGETING[0][3] = new Settings (2800.0, 0.68);
KNOWNTARGETING[0][4] = new Settings (3000.0, 0.58);
KNOWNTARGETING[0][5] = new Settings (3000.0, 0.58);
// ROW 1
KNOWNTARGETING[1][0] = new Settings (2300.0, 0.93);
KNOWNTARGETING[1][1] = new Settings (2300.0, 0.93);
KNOWNTARGETING[1][2] = new Settings (2600.0, 0.78);
KNOWNTARGETING[1][3] = new Settings (2800.0, 0.62);
KNOWNTARGETING[1][4] = new Settings (3000.0, 0.55);
KNOWNTARGETING[1][5] = new Settings (3200.0, 0.50);
// ROW 2
KNOWNTARGETING[2][0] = new Settings (2500.0, 0.78);
KNOWNTARGETING[2][1] = new Settings (2500.0, 0.78);
KNOWNTARGETING[2][2] = new Settings (2700.0, 0.60);
KNOWNTARGETING[2][3] = new Settings (2900.0, 0.53);
KNOWNTARGETING[2][4] = new Settings (3100.0, 0.50);
KNOWNTARGETING[2][5] = new Settings (3100.0, 0.50);
// ROW 3
KNOWNTARGETING[3][0] = new Settings (2900.0, 0.50);
KNOWNTARGETING[3][1] = new Settings (2900.0, 0.50);
KNOWNTARGETING[3][2] = new Settings (2900.0, 0.50);
KNOWNTARGETING[3][3] = new Settings (3100.0, 0.47);
KNOWNTARGETING[3][4] = new Settings (3100.0, 0.47);
KNOWNTARGETING[3][5] = new Settings (3100.0, 0.47);
// ROW 4
KNOWNTARGETING[4][0] = new Settings (4540.0, 0.1);
KNOWNTARGETING[4][1] = new Settings (4541.0, 0.1);
KNOWNTARGETING[4][2] = new Settings (4542.0, 0.1);
KNOWNTARGETING[4][3] = new Settings (4543.0, 0.1);
KNOWNTARGETING[4][4] = new Settings (4544.0, 0.1);
KNOWNTARGETING[4][5] = new Settings (4545.0, 0.1);
// ROW 1
KNOWNTARGETING[5][0] = new Settings (4550.0, 0.1);
KNOWNTARGETING[5][1] = new Settings (4551.0, 0.1);
KNOWNTARGETING[5][2] = new Settings (4552.0, 0.1);
KNOWNTARGETING[5][3] = new Settings (4553.0, 0.1);
KNOWNTARGETING[5][4] = new Settings (4554.0, 0.1);
KNOWNTARGETING[5][5] = new Settings (4555.0, 0.1);
}
public Targeting()
{
}
public Settings calculateSettings(double robotX, double robotY, double robotHeading, double robotVelocity, boolean interpolate) {
Settings recommendedSettings = new Settings(0.0, 0.0);
double cos45 = Math.cos(Math.toRadians(-45));
double sin45 = Math.sin(Math.toRadians(-45));
double rotatedY = (robotX + cancelOffsetX) * sin45 + (robotY + cancelOffsetY) * cos45;
double rotatedX = (robotX + cancelOffsetX) * cos45 - (robotY + cancelOffsetY) * sin45;
// Convert robot coordinates to inches
robotInchesX = rotatedX * unitConversionFactor;
robotInchesY = rotatedY * unitConversionFactor;
// Find approximate location in the grid
int gridX = Math.abs(Math.floorDiv((int) robotInchesX, tileSize) + 1);
int gridY = Math.abs(Math.floorDiv((int) robotInchesY, tileSize));
//clamp
robotGridX = Math.max(0, Math.min(gridX, KNOWNTARGETING[0].length - 1));
robotGridY = Math.max(0, Math.min(gridY, KNOWNTARGETING.length - 1));
// basic search
if(!interpolate) {
if ((robotGridY < 6) && (robotGridX <6)) {
recommendedSettings.flywheelRPM = KNOWNTARGETING[robotGridX][robotGridY].flywheelRPM;
recommendedSettings.hoodAngle = KNOWNTARGETING[robotGridX][robotGridY].hoodAngle;
}
return recommendedSettings;
} else {
// bilinear interpolation
int x0 = robotGridX;
int x1 = Math.min(x0 + 1, KNOWNTARGETING[0].length - 1);
int y0 = gridY;
int y1 = Math.min(y0 + 1, KNOWNTARGETING.length - 1);
double x = (robotInchesX - (x0 * tileSize)) / tileSize;
double y = (robotInchesY - (y0 * tileSize)) / tileSize;
double rpm00 = KNOWNTARGETING[y0][x0].flywheelRPM;
double rpm10 = KNOWNTARGETING[y0][x1].flywheelRPM;
double rpm01 = KNOWNTARGETING[y1][x0].flywheelRPM;
double rpm11 = KNOWNTARGETING[y1][x1].flywheelRPM;
double angle00 = KNOWNTARGETING[y0][x0].hoodAngle;
double angle10 = KNOWNTARGETING[y0][x1].hoodAngle;
double angle01 = KNOWNTARGETING[y1][x0].hoodAngle;
double angle11 = KNOWNTARGETING[y1][x1].hoodAngle;
recommendedSettings.flywheelRPM = (1 - x) * (1 - y) * rpm00 + x * (1 - y) * rpm10 + (1 - x) * y * rpm01 + x * y * rpm11;
recommendedSettings.hoodAngle = (1 - x) * (1 - y) * angle00 + x * (1 - y) * angle10 + (1 - x) * y * angle01 + x * y * angle11;
return recommendedSettings;
}
}
}

View File

@@ -0,0 +1,227 @@
package org.firstinspires.ftc.teamcode.utils;
import static org.firstinspires.ftc.teamcode.constants.Color.redAlliance;
import com.acmerobotics.dashboard.config.Config;
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
import com.acmerobotics.roadrunner.Pose2d;
import com.arcrobotics.ftclib.controller.PIDFController;
import com.qualcomm.hardware.limelightvision.LLResult;
import com.qualcomm.hardware.limelightvision.LLResultTypes;
import com.qualcomm.hardware.limelightvision.Limelight3A;
import com.qualcomm.robotcore.hardware.DcMotor;
import org.firstinspires.ftc.robotcore.external.navigation.Pose3D;
import java.util.List;
@Config
public class Turret {
public static double turretTolerance = 0.02;
public static double turrPosScalar = 0.00011264432;
public static double turret180Range = 0.4;
public static double turrDefault = 0.4;
// TODO: tune these values for limelight
// At the top with other static variables:
public static double kP = 0.015; // Proportional gain - tune this first
public static double kI = 0.0005; // Integral gain - add slowly if needed
public static double kD = 0.002; // Derivative gain - helps prevent overshoot
public static double kF = 0.002; // Derivative gain - helps prevent overshoot
public static double maxOffset = 10; // degrees - safety limit
// Add these as instance variables:
private double lastTagBearing = 0.0;
private double offsetIntegral = 0.0;
public static double cameraBearingEqual = 1;
public static double turrMin = 0.2;
public static double turrMax = 0.8;
public static double mult = 0.0;
private boolean lockOffset = false;
Robot robot;
MultipleTelemetry TELE;
Limelight3A webcam;
private int obeliskID = 0;
private double offset = 0.0;
private PIDFController controller = new PIDFController(kP, kI, kD, kF);
double tx = 0.0;
double ty = 0.0;
double limelightPosX = 0.0;
double limelightPosY = 0.0;
public static double clampTolerance = 0.03;
public Turret(Robot rob, MultipleTelemetry tele, Limelight3A cam) {
this.TELE = tele;
this.robot = rob;
this.webcam = cam;
webcam.start();
if (redAlliance){
webcam.pipelineSwitch(3);
} else {
webcam.pipelineSwitch(2);
}
}
public void zeroTurretEncoder() {
robot.intake.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
robot.intake.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
}
public double getTurrPos() {
return turrPosScalar * (robot.turr1Pos.getVoltage() / 3.3) + turrDefault;
}
public void manualSetTurret(double pos){
robot.turr1.setPosition(pos);
robot.turr2.setPosition(1-pos);
}
public boolean turretEqual(double pos) {
return Math.abs(pos - this.getTurrPos()) < turretTolerance;
}
private void limelightRead(){ // only for tracking purposes, not general reads
if (redAlliance){
webcam.pipelineSwitch(3);
} else {
webcam.pipelineSwitch(2);
}
LLResult result = webcam.getLatestResult();
if (result != null) {
if (result.isValid()) {
tx = result.getTx();
ty = result.getTy();
// MegaTag1 code for receiving position
Pose3D botpose = result.getBotpose();
if (botpose != null){
limelightPosX = botpose.getPosition().x;
limelightPosY = botpose.getPosition().y;
}
}
}
}
public double getBearing() {
tx = 1000;
limelightRead();
return tx;
}
public double getTy(){
limelightRead();
return ty;
}
public double getLimelightX(){
limelightRead();
return limelightPosX;
}
public double getLimelightY(){
limelightRead();
return limelightPosY;
}
public int detectObelisk() {
webcam.pipelineSwitch(1);
LLResult result = webcam.getLatestResult();
if (result != null && result.isValid()) {
List<LLResultTypes.FiducialResult> fiducials = result.getFiducialResults();
for (LLResultTypes.FiducialResult fiducial : fiducials) {
obeliskID = fiducial.getFiducialId();
}
}
return obeliskID;
}
public int getObeliskID() {
return obeliskID;
}
public void zeroOffset() {
offset = 0.0;
}
public void lockOffset(boolean lock) {
lockOffset = lock;
}
/*
Param @deltaPos = Pose2d when subtracting robot x, y, heading from goal x, y, heading
*/
public void trackGoal(Pose2d deltaPos) {
controller.setPIDF(kP, kI, kD, kF);
/* ---------------- FIELD → TURRET GEOMETRY ---------------- */
// Angle from robot to goal in robot frame
double desiredTurretAngleDeg = Math.toDegrees(
Math.atan2(deltaPos.position.y, deltaPos.position.x)
);
// Robot heading (field → robot)
double robotHeadingDeg = Math.toDegrees(deltaPos.heading.toDouble());
// Turret angle needed relative to robot
double turretAngleDeg = desiredTurretAngleDeg - robotHeadingDeg;
turretAngleDeg = -turretAngleDeg;
// Normalize to [-180, 180]
while (turretAngleDeg > 180) turretAngleDeg -= 360;
while (turretAngleDeg < -180) turretAngleDeg += 360;
/* ---------------- APRILTAG CORRECTION ---------------- */
//
double tagBearingDeg = getBearing(); // + = target is to the left
turretAngleDeg += offset;
/* ---------------- ANGLE → SERVO ---------------- */
double turretPos = turrDefault + (turretAngleDeg * (turret180Range * 2.0) / 360);
// Clamp to servo range
double currentEncoderPos = this.getTurrPos();
if (!turretEqual(turretPos)) {
double diff = turretPos - currentEncoderPos;
turretPos = turretPos + diff * mult;
}
if (currentEncoderPos < (turrMin + clampTolerance) || currentEncoderPos > (turrMax - clampTolerance)) {
// Clamp to servo range
turretPos = Math.max(turrMin, Math.min(turretPos, turrMax));
} else { // TODO: add so it only adds error when standstill
if (tagBearingDeg != 1000.0 && Math.abs(tagBearingDeg) > cameraBearingEqual && !lockOffset) {
// PID-based offset correction for faster, smoother tracking
// Proportional: respond to current error
offset = -controller.calculate(tagBearingDeg);
}
}
robot.turr1.setPosition(turretPos);
robot.turr2.setPosition(1.0 - turretPos);
/* ---------------- TELEMETRY ---------------- */
TELE.addData("Turret Angle", turretAngleDeg);
TELE.addData("Bearing", tagBearingDeg);
TELE.addData("Offset", offset);
}
}

View File

@@ -25,5 +25,9 @@ allprojects {
}
repositories {
mavenCentral()
repositories {
mavenCentral()
google()
maven { url 'https://maven.pedropathing.com' }
}
}