Adding all files
This commit is contained in:
33
TeamCode/build.gradle
Normal file
33
TeamCode/build.gradle
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// build.gradle in TeamCode
|
||||
//
|
||||
// Most of the definitions for building your module reside in a common, shared
|
||||
// file 'build.common.gradle'. Being factored in this way makes it easier to
|
||||
// integrate updates to the FTC into your code. If you really need to customize
|
||||
// the build definitions, you can place those customizations in this file, but
|
||||
// please think carefully as to whether such customizations are really necessary
|
||||
// before doing so.
|
||||
|
||||
|
||||
// Custom definitions may go here
|
||||
|
||||
// Include common definitions from above.
|
||||
apply from: '../build.common.gradle'
|
||||
apply from: '../build.dependencies.gradle'
|
||||
|
||||
android {
|
||||
namespace = 'org.firstinspires.ftc.teamcode'
|
||||
|
||||
packagingOptions {
|
||||
jniLibs.useLegacyPackaging true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':FtcRobotController')
|
||||
implementation 'org.ftclib.ftclib:core:2.1.1' // core
|
||||
implementation 'org.ftclib.ftclib:vision:2.1.0' // vision
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.7'
|
||||
implementation 'com.acmerobotics.roadrunner:core:0.5.6'
|
||||
}
|
||||
BIN
TeamCode/lib/OpModeAnnotationProcessor.jar
Normal file
BIN
TeamCode/lib/OpModeAnnotationProcessor.jar
Normal file
Binary file not shown.
11
TeamCode/src/main/AndroidManifest.xml
Normal file
11
TeamCode/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- Note: the actual manifest file used in your APK merges this file with contributions
|
||||
from the modules on which your app depends (such as FtcRobotController, etc).
|
||||
So it won't ultimately be as empty as it might here appear to be :-) -->
|
||||
|
||||
<!-- The package name here determines the package for your R class and your BuildConfig class -->
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.control.PIDFController;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.geometry.Vector2d;
|
||||
import com.arcrobotics.ftclib.drivebase.MecanumDrive;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadEx;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadKeys;
|
||||
import com.arcrobotics.ftclib.hardware.motors.Motor;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.Gamepad;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.advanced.PoseStorage;
|
||||
|
||||
|
||||
@TeleOp (name = "DO NOT RUN")
|
||||
@Config
|
||||
public class AlignmentTest extends LinearOpMode {
|
||||
|
||||
// Define 2 states, driver control or alignment control
|
||||
enum Mode {
|
||||
NORMAL_CONTROL,
|
||||
ALIGN_TO_POINT
|
||||
}
|
||||
|
||||
private Mode currentMode = Mode.NORMAL_CONTROL;
|
||||
|
||||
// Declare a PIDF Controller to regulate heading
|
||||
// Use the same gains as SampleMecanumDrive's heading controller
|
||||
private PIDFController headingController = new PIDFController(SampleMecanumDrive.HEADING_PID);
|
||||
|
||||
// Declare a target vector you'd like your bot to align with
|
||||
// Can be any x/y coordinate of your choosing
|
||||
private Vector2d targetPosition = new Vector2d(0, 90);
|
||||
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
|
||||
/** SETUP **/
|
||||
|
||||
// Hardware Map Setup
|
||||
Hware robot = new Hware();
|
||||
robot.initialize(hardwareMap);
|
||||
|
||||
// Gamepad Setup
|
||||
GamepadEx driverControl = new GamepadEx(gamepad1);
|
||||
GamepadEx armControl = new GamepadEx(gamepad2);
|
||||
|
||||
// DriveBase Setup
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
double SPEEDCONTROL = 1;
|
||||
|
||||
// Retrieve pose
|
||||
drive.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
drive.getLocalizer().setPoseEstimate(PoseStorage.currentPose);
|
||||
headingController.setInputBounds(-Math.PI, Math.PI);
|
||||
|
||||
waitForStart();
|
||||
|
||||
while (opModeIsActive()) {
|
||||
|
||||
/** DRIVER CONTROLS **/
|
||||
|
||||
if (driverControl.getButton(GamepadKeys.Button.RIGHT_BUMPER) || driverControl.getButton(GamepadKeys.Button.LEFT_BUMPER)) { SPEEDCONTROL = .5; } else { SPEEDCONTROL = 1; }
|
||||
|
||||
// Read pose
|
||||
Pose2d poseEstimate = drive.getLocalizer().getPoseEstimate();
|
||||
|
||||
// Declare a drive direction
|
||||
// Pose representing desired x, y, and angular velocity
|
||||
Pose2d driveDirection = new Pose2d();
|
||||
|
||||
telemetry.addData("mode", currentMode);
|
||||
|
||||
switch (currentMode) {
|
||||
case NORMAL_CONTROL:
|
||||
// Switch into alignment mode if `a` is pressed
|
||||
if (gamepad1.triangle) {
|
||||
currentMode = Mode.ALIGN_TO_POINT;
|
||||
}
|
||||
|
||||
// Standard teleop control
|
||||
// Convert gamepad input into desired pose velocity
|
||||
driveDirection = new Pose2d(
|
||||
-gamepad1.left_stick_y * SPEEDCONTROL,
|
||||
-gamepad1.left_stick_x * SPEEDCONTROL,
|
||||
-gamepad1.right_stick_x * SPEEDCONTROL
|
||||
);
|
||||
break;
|
||||
case ALIGN_TO_POINT:
|
||||
// Switch back into normal driver control mode if `b` is pressed
|
||||
if (gamepad1.circle) {
|
||||
currentMode = Mode.NORMAL_CONTROL;
|
||||
}
|
||||
|
||||
// Create a vector from the gamepad x/y inputs which is the field relative movement
|
||||
// Then, rotate that vector by the inverse of that heading for field centric control
|
||||
Vector2d fieldFrameInput = new Vector2d(
|
||||
-gamepad1.left_stick_y * SPEEDCONTROL,
|
||||
-gamepad1.left_stick_x * SPEEDCONTROL
|
||||
);
|
||||
Vector2d robotFrameInput = fieldFrameInput.rotated(-poseEstimate.getHeading());
|
||||
|
||||
// Difference between the target vector and the bot's position
|
||||
Vector2d difference = targetPosition.minus(poseEstimate.vec());
|
||||
// Obtain the target angle for feedback and derivative for feedforward
|
||||
double theta = difference.angle();
|
||||
|
||||
// Not technically omega because its power. This is the derivative of atan2
|
||||
double thetaFF = -fieldFrameInput.rotated(-Math.PI / 2).dot(difference) / (difference.norm() * difference.norm());
|
||||
|
||||
// Set the target heading for the heading controller to our desired angle
|
||||
headingController.setTargetPosition(theta);
|
||||
|
||||
// Set desired angular velocity to the heading controller output + angular
|
||||
// velocity feedforward
|
||||
double headingInput = (headingController.update(poseEstimate.getHeading())
|
||||
* DriveConstants.kV + thetaFF)
|
||||
* DriveConstants.TRACK_WIDTH;
|
||||
|
||||
// Combine the field centric x/y velocity with our derived angular velocity
|
||||
driveDirection = new Pose2d(
|
||||
robotFrameInput,
|
||||
headingInput
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
drive.setWeightedDrivePower(driveDirection);
|
||||
|
||||
// Update the heading controller with our current heading
|
||||
headingController.update(poseEstimate.getHeading());
|
||||
|
||||
// Update the localizer
|
||||
drive.getLocalizer().update();
|
||||
// Print pose to telemetry
|
||||
telemetry.addData("x", poseEstimate.getX());
|
||||
telemetry.addData("y", poseEstimate.getY());
|
||||
telemetry.addData("heading", poseEstimate.getHeading());
|
||||
telemetry.update();
|
||||
|
||||
}
|
||||
|
||||
while (!isStopRequested() && opModeIsActive());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import android.util.Size;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.arcrobotics.ftclib.drivebase.MecanumDrive;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadEx;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadKeys;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.util.ElapsedTime;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.Position;
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles;
|
||||
import org.firstinspires.ftc.vision.VisionPortal;
|
||||
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection;
|
||||
import org.firstinspires.ftc.vision.apriltag.AprilTagGameDatabase;
|
||||
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor;
|
||||
|
||||
import java.util.List;
|
||||
@Config
|
||||
@TeleOp(name = "Auto-aiming turret (Webcam AprilTag)", group = "Iterative Opmode")
|
||||
public class AutoLock extends OpMode {
|
||||
|
||||
// Hardware
|
||||
private DcMotorEx turretMotor;
|
||||
|
||||
// Vision
|
||||
private VisionPortal visionPortal;
|
||||
private AprilTagProcessor aprilTag;
|
||||
|
||||
// PID gains
|
||||
public double kP = 0.0012;
|
||||
public static double kI = 0.001;
|
||||
public static double kD = 0.002;
|
||||
|
||||
// Target center (degrees). 0 means center of camera.
|
||||
private double targetX = 0.0;
|
||||
|
||||
private double integral = 0.0;
|
||||
private double lastError = 0.0;
|
||||
|
||||
GamepadEx driverControl = null;
|
||||
GamepadEx armControl = null;
|
||||
|
||||
private final ElapsedTime loopTimer = new ElapsedTime();
|
||||
private final ElapsedTime targetLostTimer = new ElapsedTime();
|
||||
|
||||
// Tuning
|
||||
private static final double POSITION_TOLERANCE_DEG = 1.5;
|
||||
private static final double MIN_POWER = 0.05;
|
||||
private static final double MAX_POWER = 0.4;
|
||||
private static final double TARGET_LOST_TIMEOUT_SEC = 0.5;
|
||||
|
||||
// Flip if turret moves the wrong way
|
||||
private static final boolean INVERT_MOTOR = true;
|
||||
|
||||
// OPTIONAL: only lock onto certain tag IDs. Leave empty to accept any.
|
||||
// Example: new int[]{1,2,3}
|
||||
private static final int[] DESIRED_TAG_IDS = new int[]{23, 24}; // empty = any
|
||||
|
||||
private boolean targetWasVisible = false;
|
||||
private Position cameraPosition = new Position(DistanceUnit.INCH,
|
||||
0, 0, 0, 0);
|
||||
private YawPitchRollAngles cameraOrientation = new YawPitchRollAngles(AngleUnit.DEGREES,
|
||||
0, -90, 0, 0);
|
||||
|
||||
// Drive Variables
|
||||
double x, y, xy;
|
||||
double SPEEDCONTROL;
|
||||
boolean driveMode = true;
|
||||
MecanumDrive drive = null;
|
||||
Hware robot = null;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
try {
|
||||
|
||||
// Hardware Map Setup
|
||||
robot = new Hware();
|
||||
robot.initialize(hardwareMap);
|
||||
|
||||
driverControl = new GamepadEx(gamepad1);
|
||||
armControl = new GamepadEx(gamepad2);
|
||||
|
||||
// DriveBase Setup
|
||||
drive = new MecanumDrive(true, robot.frontLeft, robot.frontRight, robot.backLeft, robot.backRight);
|
||||
|
||||
|
||||
turretMotor = hardwareMap.get(DcMotorEx.class, "turret");
|
||||
turretMotor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
|
||||
// AprilTag processor
|
||||
aprilTag = new AprilTagProcessor.Builder()
|
||||
.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11)
|
||||
.setTagLibrary(AprilTagGameDatabase.getDecodeTagLibrary())
|
||||
.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES)
|
||||
// If you have a Logitech C920/C270 etc, you can usually leave defaults.
|
||||
// If you have calibrated intrinsics, you can set them here (advanced).
|
||||
.build();
|
||||
|
||||
// Vision portal with webcam
|
||||
visionPortal = new VisionPortal.Builder()
|
||||
.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
|
||||
.addProcessor(aprilTag)
|
||||
.build();
|
||||
|
||||
|
||||
loopTimer.reset();
|
||||
targetLostTimer.reset();
|
||||
|
||||
telemetry.addData("Status", "Initialized (Webcam + AprilTag)");
|
||||
telemetry.addData("Motor Inverted", INVERT_MOTOR);
|
||||
} catch (Exception e) {
|
||||
telemetry.addData("Init Error", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loop() {
|
||||
try {
|
||||
// Get current detections
|
||||
List<AprilTagDetection> detections = aprilTag.getDetections();
|
||||
|
||||
AprilTagDetection best = pickBestDetection(detections);
|
||||
|
||||
telemetry.addData("Detections", detections.size());
|
||||
for (AprilTagDetection d : detections) {
|
||||
telemetry.addData("ID", d.id);
|
||||
telemetry.addData("Metadata null?", d.metadata == null);
|
||||
telemetry.addData("Pose null?", d.ftcPose == null);
|
||||
|
||||
}
|
||||
|
||||
/** DRIVER CONTROLS **/
|
||||
|
||||
if (gamepad2.x) { driveMode = true; } else if (gamepad2.y) { driveMode = false;}
|
||||
|
||||
// Chassis
|
||||
if (!driveMode) { SPEEDCONTROL = 0;} else if (driverControl.getButton(GamepadKeys.Button.RIGHT_BUMPER) || driverControl.getButton(GamepadKeys.Button.LEFT_BUMPER)) { SPEEDCONTROL = .5; } else { SPEEDCONTROL = 1; }
|
||||
x = driverControl.getLeftX();
|
||||
y = driverControl.getLeftY();
|
||||
xy = driverControl.getRightX();
|
||||
|
||||
drive.driveRobotCentric(
|
||||
-x * SPEEDCONTROL,
|
||||
-y * SPEEDCONTROL,
|
||||
-xy * SPEEDCONTROL
|
||||
);
|
||||
|
||||
// Intake
|
||||
if (driverControl.getTrigger(GamepadKeys.Trigger.RIGHT_TRIGGER) > 0.5) { robot.intake.set(1); } else if (driverControl.getTrigger(GamepadKeys.Trigger.LEFT_TRIGGER) > 0.5) { robot.intake.set(-1); } else { robot.intake.set(0); }
|
||||
|
||||
/** Arm Controls **/
|
||||
// if (armControl.getTrigger(GamepadKeys.Trigger.RIGHT_TRIGGER) > 0.5) { robot.turret.set(.3); } else if (armControl.getTrigger(GamepadKeys.Trigger.LEFT_TRIGGER) > 0.5) { robot.turret.set(-0.3); } else { robot.turret.set(0); }
|
||||
// if (armControl.getButton(GamepadKeys.Button.X)) { robot.launchLeft.set(1); robot.launchRight.set(1); } else {robot.launchLeft.set(0); robot.launchRight.set(0);}
|
||||
// if (!driveMode) {robot.rightPTO.setPosition(1); robot.leftPTO.setPosition(1);} else {robot.rightPTO.setPosition(0); robot.rightPTO.setPosition(0);}
|
||||
|
||||
|
||||
if (best != null) {
|
||||
targetWasVisible = true;
|
||||
targetLostTimer.reset();
|
||||
|
||||
// ----- "tx" equivalent -----
|
||||
// AprilTag gives pose relative to camera. We want horizontal angle offset.
|
||||
// Use bearing/yaw style value if available from metadata or pose.
|
||||
//
|
||||
// Most reliable: compute from pose X/Z (left/right vs forward):
|
||||
// angle = atan2(x, z)
|
||||
//
|
||||
// In FTC pose:
|
||||
// - pose.x: left/right (inches or meters depending on config; typically inches)
|
||||
// - pose.z: forward distance
|
||||
//
|
||||
// If your coordinate axes differ, flip sign once using INVERT_MOTOR or sign flip here.
|
||||
double x = best.ftcPose.x; // left(+) / right(-) depending on convention
|
||||
double z = best.ftcPose.z; // forward distance
|
||||
double txDeg = Math.toDegrees(Math.atan2(x, z));
|
||||
|
||||
// Timing
|
||||
double dt = loopTimer.seconds();
|
||||
loopTimer.reset();
|
||||
|
||||
if (dt < 0.001) dt = 0.001;
|
||||
if (dt > 1.0) dt = 1.0;
|
||||
|
||||
// Error (positive tx means tag is to one side)
|
||||
double error = txDeg - targetX;
|
||||
|
||||
// PID
|
||||
integral += error * dt;
|
||||
integral = Math.max(-50, Math.min(50, integral)); // anti-windup
|
||||
|
||||
double derivative = (error - lastError) / dt;
|
||||
|
||||
double pidOutput = (kP * error) + (kI * integral) + (kD * derivative);
|
||||
|
||||
// Deadband
|
||||
if (Math.abs(error) < POSITION_TOLERANCE_DEG) {
|
||||
pidOutput = 0;
|
||||
integral = 0;
|
||||
}
|
||||
|
||||
// Min power to overcome friction
|
||||
if (pidOutput != 0 && Math.abs(pidOutput) < MIN_POWER) {
|
||||
pidOutput = MIN_POWER * Math.signum(pidOutput);
|
||||
}
|
||||
|
||||
// Clamp
|
||||
double motorPower = clamp(pidOutput, -MAX_POWER, MAX_POWER);
|
||||
|
||||
if (INVERT_MOTOR) motorPower = -motorPower;
|
||||
|
||||
if (!Double.isFinite(motorPower)) {
|
||||
motorPower = 0;
|
||||
resetPID();
|
||||
}
|
||||
|
||||
turretMotor.setPower(motorPower);
|
||||
lastError = error;
|
||||
|
||||
telemetry.addData("Status", "LOCKED ON");
|
||||
telemetry.addData("Tag ID", best.id);
|
||||
telemetry.addData("tx (deg)", "%.2f", txDeg);
|
||||
telemetry.addData("Error", "%.2f", error);
|
||||
telemetry.addData("PID Output", "%.3f", pidOutput);
|
||||
telemetry.addData("Motor Power", "%.3f", motorPower);
|
||||
|
||||
// Extra pose telemetry (helpful for debugging)
|
||||
telemetry.addData("Pose x", "%.2f", best.ftcPose.x);
|
||||
telemetry.addData("Pose z", "%.2f", best.ftcPose.z);
|
||||
telemetry.addData("Range: ", best.ftcPose.range);
|
||||
telemetry.addData("Bearing", "%.2f deg", best.ftcPose.bearing);
|
||||
telemetry.addData("Yaw", "%.2f deg", best.ftcPose.yaw);
|
||||
|
||||
if(best.ftcPose.range < 40){
|
||||
kP = 0.008;
|
||||
} else if (best.ftcPose.range < 70) {
|
||||
kP = 0.005;
|
||||
} else if (best.ftcPose.range < 90){
|
||||
kP = 0.004;
|
||||
} else if (best.ftcPose.range < 110) {
|
||||
kP = 0.003;
|
||||
} else if (best.ftcPose.range < 130) {
|
||||
kP = 0.002;
|
||||
} else if (best.ftcPose.range < 150) {
|
||||
kP = 0.001;
|
||||
} else {
|
||||
kP = 0.0005;
|
||||
}
|
||||
telemetry.addData("kP: ", kP);
|
||||
|
||||
} else {
|
||||
handleNoTarget();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
telemetry.addData("Error", e.getMessage());
|
||||
telemetry.addData("Error Type", e.getClass().getSimpleName());
|
||||
stopTurret();
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick "best" detection. Simple approach:
|
||||
* - If DESIRED_TAG_IDS set: only accept those
|
||||
* - Choose closest (smallest range) among candidates
|
||||
*/
|
||||
private AprilTagDetection pickBestDetection(List<AprilTagDetection> detections) {
|
||||
if (detections == null || detections.isEmpty()) return null;
|
||||
|
||||
AprilTagDetection best = null;
|
||||
double bestRange = Double.POSITIVE_INFINITY;
|
||||
|
||||
for (AprilTagDetection d : detections) {
|
||||
if (d == null) continue;
|
||||
|
||||
if (!isDesiredId(d.id)) continue;
|
||||
|
||||
// Use range as "quality" metric
|
||||
double r = d.ftcPose.range;
|
||||
if (r < bestRange) {
|
||||
bestRange = r;
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private boolean isDesiredId(int id) {
|
||||
if (DESIRED_TAG_IDS == null || DESIRED_TAG_IDS.length == 0) return true;
|
||||
for (int x : DESIRED_TAG_IDS) {
|
||||
if (x == id) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleNoTarget() {
|
||||
if (targetWasVisible && targetLostTimer.seconds() < TARGET_LOST_TIMEOUT_SEC) {
|
||||
telemetry.addData("Status", "TRACKING LOST - Coasting");
|
||||
telemetry.addData("Time Lost", "%.2f s", targetLostTimer.seconds());
|
||||
// You could also keep last motor power for a brief time if you store it.
|
||||
} else {
|
||||
stopTurret();
|
||||
telemetry.addData("Status", "SEARCHING");
|
||||
telemetry.addData("Target Visible", "No");
|
||||
}
|
||||
}
|
||||
|
||||
private void stopTurret() {
|
||||
if (turretMotor != null) turretMotor.setPower(0);
|
||||
resetPID();
|
||||
targetWasVisible = false;
|
||||
}
|
||||
|
||||
private void resetPID() {
|
||||
integral = 0;
|
||||
lastError = 0;
|
||||
}
|
||||
|
||||
private static double clamp(double v, double lo, double hi) {
|
||||
return Math.max(lo, Math.min(hi, v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
try {
|
||||
if (turretMotor != null) turretMotor.setPower(0);
|
||||
if (visionPortal != null) visionPortal.close();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.firstinspires.ftc.teamcode.Auton;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryVelocityConstraint;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.Hware;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.advanced.PoseStorage;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
|
||||
@Config
|
||||
@Autonomous(name = "Blue Depot Cycle")
|
||||
public class blueDepot extends LinearOpMode {
|
||||
|
||||
Hware robot = new Hware();
|
||||
double TICKS_PER_DEGREE = (384.5 * 9.26) / 360;
|
||||
|
||||
public void turretPower(double power) {
|
||||
robot.turret.setPower(power);
|
||||
}
|
||||
|
||||
public void turretPos(int position) {
|
||||
turretPower(0);
|
||||
robot.turret.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
||||
robot.turret.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
||||
robot.turret.setTargetPosition(position * (int) TICKS_PER_DEGREE);
|
||||
robot.turret.setMode(DcMotor.RunMode.RUN_TO_POSITION);
|
||||
turretPower(1);
|
||||
}
|
||||
|
||||
public void spintakeStart(){
|
||||
robot.intake.set(1);
|
||||
}
|
||||
|
||||
public void spintakeEnd(){
|
||||
robot.intake.set(0);
|
||||
}
|
||||
|
||||
public void shoot(){
|
||||
robot.topFly.set(1);
|
||||
robot.bottomFly.set(1);
|
||||
robot.activeTransfer.setPosition(1);
|
||||
}
|
||||
|
||||
public void reset(){
|
||||
robot.intake.set(0);
|
||||
robot.topFly.set(0);
|
||||
robot.bottomFly.set(0);
|
||||
robot.launchHood.setPosition(0.3);
|
||||
robot.activeTransfer.setPosition(0);
|
||||
}
|
||||
|
||||
|
||||
public void runOpMode() throws InterruptedException {
|
||||
|
||||
//webcam1 = hardwareMap.get(WebcamName.class, "Webcam 2");
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
|
||||
Pose2d startPose = new Pose2d(0, 0, Math.toRadians(0));
|
||||
|
||||
drive.setPoseEstimate(startPose);
|
||||
|
||||
TrajectorySequence oneCycle = drive.trajectorySequenceBuilder(startPose)
|
||||
.strafeLeft(25)
|
||||
.UNSTABLE_addTemporalMarkerOffset(.3, () -> {
|
||||
shoot();
|
||||
})
|
||||
.waitSeconds(1)
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
reset();
|
||||
})
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
spintakeStart();
|
||||
})
|
||||
.forward(30, SampleMecanumDrive.getVelocityConstraint(30, DriveConstants.MAX_ANG_VEL, DriveConstants.TRACK_WIDTH),
|
||||
SampleMecanumDrive.getAccelerationConstraint(DriveConstants.MAX_ACCEL))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
spintakeEnd();
|
||||
})
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
shoot();
|
||||
})
|
||||
.waitSeconds(1)
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
reset();
|
||||
})
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.forward(10)
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
spintakeStart();
|
||||
})
|
||||
.strafeLeft(10)
|
||||
.forward(3)
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
spintakeEnd();
|
||||
})
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
shoot();
|
||||
})
|
||||
.waitSeconds(1)
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
spintakeStart();
|
||||
})
|
||||
.forward(30, SampleMecanumDrive.getVelocityConstraint(30, DriveConstants.MAX_ANG_VEL, DriveConstants.TRACK_WIDTH),
|
||||
SampleMecanumDrive.getAccelerationConstraint(DriveConstants.MAX_ACCEL))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
spintakeEnd();
|
||||
})
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
shoot();
|
||||
})
|
||||
.waitSeconds(1)
|
||||
.UNSTABLE_addTemporalMarkerOffset(.1, () -> {
|
||||
reset();
|
||||
})
|
||||
.lineToLinearHeading(new Pose2d(0,0, Math.toRadians(0)))
|
||||
.build();
|
||||
|
||||
waitForStart();
|
||||
if (opModeIsActive() && !isStopRequested()) {
|
||||
// while (opModeIsActive()) {
|
||||
drive.followTrajectorySequence(oneCycle);
|
||||
PoseStorage.currentPose = drive.getPoseEstimate();
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadEx;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadKeys;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@TeleOp
|
||||
@Config
|
||||
public class DriverControlsDoubleV1 extends LinearOpMode {
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
Hware robot = new Hware();
|
||||
robot.initialize(hardwareMap);
|
||||
|
||||
GamepadEx driverControl = new GamepadEx(gamepad1);
|
||||
GamepadEx armControl = new GamepadEx(gamepad2);
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
boolean xPrev = false;
|
||||
boolean bPrev = false;
|
||||
|
||||
final boolean[] shooting = { false };
|
||||
|
||||
Pose2d startPose = drive.getPoseEstimate();
|
||||
|
||||
TrajectorySequence shootSeq = drive.trajectorySequenceBuilder(startPose)
|
||||
.addTemporalMarker(0.00, () -> {
|
||||
robot.intake.set(0);
|
||||
robot.activeTransfer.setPosition(0.7);
|
||||
robot.topFly.set(1);
|
||||
robot.bottomFly.set(1);
|
||||
})
|
||||
.waitSeconds(0.50)
|
||||
.addTemporalMarker(0.00, () -> {
|
||||
robot.activeTransfer.setPosition(1);
|
||||
robot.intake.set(1);
|
||||
})
|
||||
.waitSeconds(0.70)
|
||||
.addTemporalMarker(0.00, () -> {
|
||||
stopAll(robot);
|
||||
shooting[0] = false;
|
||||
})
|
||||
.build();
|
||||
|
||||
waitForStart();
|
||||
|
||||
while (opModeIsActive()) {
|
||||
|
||||
drive.update();
|
||||
|
||||
if (!shooting[0]) {
|
||||
if (driverControl.getTrigger(GamepadKeys.Trigger.RIGHT_TRIGGER) > 0.5) {
|
||||
robot.intake.set(1);
|
||||
robot.activeTransfer.setPosition(0.7);
|
||||
} else if (driverControl.getTrigger(GamepadKeys.Trigger.LEFT_TRIGGER) > 0.5) {
|
||||
robot.intake.set(-1);
|
||||
} else {
|
||||
robot.intake.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
boolean xNow = armControl.getButton(GamepadKeys.Button.X);
|
||||
if (xNow && !xPrev && !shooting[0]) {
|
||||
shooting[0] = true;
|
||||
drive.followTrajectorySequenceAsync(shootSeq);
|
||||
}
|
||||
xPrev = xNow;
|
||||
|
||||
boolean bNow = armControl.getButton(GamepadKeys.Button.B);
|
||||
if (bNow && !bPrev && shooting[0]) {
|
||||
cancelTrajectorySequenceCompat(drive);
|
||||
stopAll(robot);
|
||||
shooting[0] = false;
|
||||
}
|
||||
bPrev = bNow;
|
||||
|
||||
telemetry.addData("Shooting", shooting[0]);
|
||||
telemetry.addData("RR Busy", drive.isBusy());
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
|
||||
private static void stopAll(Hware robot) {
|
||||
robot.intake.set(0);
|
||||
robot.topFly.set(0);
|
||||
robot.bottomFly.set(0);
|
||||
robot.activeTransfer.setPosition(0.7);
|
||||
}
|
||||
|
||||
private static void cancelTrajectorySequenceCompat(Object drive) {
|
||||
if (invokeIfExists(drive, "cancelTrajectorySequence")) return;
|
||||
if (invokeIfExists(drive, "cancelFollowing")) return;
|
||||
if (invokeIfExists(drive, "breakFollowing")) return;
|
||||
try {
|
||||
Method m = drive.getClass().getMethod("setDrivePower", Pose2d.class);
|
||||
m.invoke(drive, new Pose2d(0, 0, 0));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private static boolean invokeIfExists(Object obj, String methodName) {
|
||||
try {
|
||||
Method m = obj.getClass().getMethod(methodName);
|
||||
m.invoke(obj);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import com.arcrobotics.ftclib.hardware.ServoEx;
|
||||
import com.arcrobotics.ftclib.hardware.SimpleServo;
|
||||
import com.arcrobotics.ftclib.hardware.motors.Motor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
|
||||
public class Hware {
|
||||
|
||||
HardwareMap hardwareMap;
|
||||
public Motor frontRight, frontLeft, backRight, backLeft, intake, bottomFly, topFly;
|
||||
public DcMotor turret;
|
||||
public SimpleServo activeTransfer, rightPTO, leftPTO, launchHood;
|
||||
WebcamName webcam;
|
||||
public void initialize(HardwareMap hwMap) {
|
||||
hardwareMap = hwMap;
|
||||
|
||||
/** MOTORS **/
|
||||
|
||||
// Chassis Motors
|
||||
frontRight = new Motor(hardwareMap, "frontRight", Motor.GoBILDA.RPM_435);
|
||||
frontLeft = new Motor(hardwareMap, "frontLeft", Motor.GoBILDA.RPM_435);
|
||||
backRight = new Motor(hardwareMap, "backRight", Motor.GoBILDA.RPM_435);
|
||||
backLeft = new Motor(hardwareMap, "backLeft", Motor.GoBILDA.RPM_435);
|
||||
|
||||
// Intake
|
||||
intake = new Motor(hardwareMap, "intake", Motor.GoBILDA.RPM_1620);
|
||||
|
||||
// Launch
|
||||
bottomFly = new Motor(hardwareMap, "bottomFly", Motor.GoBILDA.BARE);
|
||||
topFly = new Motor(hardwareMap, "topFly", Motor.GoBILDA.BARE);
|
||||
|
||||
// Turret
|
||||
turret = hardwareMap.get(DcMotorEx.class, "turret");
|
||||
|
||||
|
||||
/** SERVOS **/
|
||||
activeTransfer = new SimpleServo(hardwareMap, "activeTransfer", 0, 100);
|
||||
leftPTO = new SimpleServo(hardwareMap, "leftPTO", 0, 90);
|
||||
rightPTO = new SimpleServo(hardwareMap, "rightPTO", 0, 90);
|
||||
launchHood = new SimpleServo(hardwareMap, "launchHood", 0, 180);
|
||||
|
||||
/** Cam **/
|
||||
// webcam = hardwareMap.get(WebcamName.class, "cam");
|
||||
|
||||
// Zero Power Behaviors
|
||||
frontRight.setZeroPowerBehavior(Motor.ZeroPowerBehavior.BRAKE);
|
||||
frontLeft.setZeroPowerBehavior(Motor.ZeroPowerBehavior.BRAKE);
|
||||
backLeft.setZeroPowerBehavior(Motor.ZeroPowerBehavior.BRAKE);
|
||||
backRight.setZeroPowerBehavior(Motor.ZeroPowerBehavior.BRAKE);
|
||||
intake.setZeroPowerBehavior(Motor.ZeroPowerBehavior.BRAKE);
|
||||
|
||||
bottomFly.setInverted(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.control.PIDFController;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.geometry.Vector2d;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadEx;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadKeys;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDriveRIGGED;
|
||||
import org.firstinspires.ftc.teamcode.drive.advanced.PoseStorage;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
import org.firstinspires.ftc.teamcode.util.PersonalPID;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
@TeleOp (name = "OdoAutoLock")
|
||||
@Config
|
||||
public class OdometryAutoLock extends LinearOpMode {
|
||||
|
||||
// Declare a PIDF Controller to regulate heading
|
||||
// Use the same gains as SampleMecanumDrive's heading controller
|
||||
private PIDFController headingController = new PIDFController(SampleMecanumDrive.HEADING_PID);
|
||||
public static double MAX_LOCK_ANGLE = Math.toRadians(90); // ±90°
|
||||
|
||||
// Declare a target vector you'd like your bot to align with
|
||||
// Can be any x/y coordinate of your choosing
|
||||
private Vector2d targetPosition = new Vector2d(0, 45);
|
||||
|
||||
private PersonalPID controller;
|
||||
|
||||
public static double p = 0.025, i = 0, d = 0.0001, f = 0.001;
|
||||
|
||||
public static int target = 0;
|
||||
|
||||
public static double TICKS_PER_DEGREE = (384.5 * 9.26) / 360;
|
||||
public static int TURRET_MIN_TICKS = (int) (TICKS_PER_DEGREE * (-90));
|
||||
public static int TURRET_MAX_TICKS = (int) (TICKS_PER_DEGREE * (90));
|
||||
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
|
||||
/** SETUP **/
|
||||
|
||||
// Hardware Map Setup
|
||||
Hware robot = new Hware();
|
||||
robot.initialize(hardwareMap);
|
||||
controller = new PersonalPID(p, i, d, f);
|
||||
|
||||
// Gamepad Setup
|
||||
GamepadEx driverControl = new GamepadEx(gamepad1);
|
||||
GamepadEx armControl = new GamepadEx(gamepad2);
|
||||
|
||||
|
||||
// DriveBase Setup
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
SampleMecanumDriveRIGGED driveRIGGED = new SampleMecanumDriveRIGGED(hardwareMap);
|
||||
|
||||
double SPEEDCONTROL = 1;
|
||||
Boolean auto = false;
|
||||
// Retrieve pose
|
||||
drive.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
drive.getLocalizer().setPoseEstimate(PoseStorage.currentPose);
|
||||
headingController.setInputBounds(-Math.PI, Math.PI);
|
||||
|
||||
boolean xPrev = false;
|
||||
boolean bPrev = false;
|
||||
|
||||
final boolean[] shooting = { false };
|
||||
|
||||
Pose2d startPose = drive.getPoseEstimate();
|
||||
|
||||
TrajectorySequence shootSeq = driveRIGGED.trajectorySequenceBuilder(startPose)
|
||||
.forward(.1)
|
||||
.addDisplacementMarker(()-> {
|
||||
robot.activeTransfer.setPosition(1);
|
||||
robot.intake.set(1);
|
||||
})
|
||||
.build();
|
||||
|
||||
TrajectorySequence runUp = driveRIGGED.trajectorySequenceBuilder(startPose)
|
||||
.forward(.1)
|
||||
.addDisplacementMarker(()-> {
|
||||
robot.topFly.set(1);
|
||||
robot.bottomFly.set(1);
|
||||
})
|
||||
.forward(.1)
|
||||
.waitSeconds(1.5)
|
||||
.forward(.1)
|
||||
.addDisplacementMarker(()-> {
|
||||
gamepad1.rumbleBlips(2);
|
||||
gamepad2.rumbleBlips(2);
|
||||
})
|
||||
.build();
|
||||
|
||||
|
||||
waitForStart();
|
||||
|
||||
robot.turret.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
||||
|
||||
while (opModeIsActive()) {
|
||||
|
||||
double distance = Math.hypot(drive.getPoseEstimate().getX() - targetPosition.getX(), drive.getPoseEstimate().getY() - targetPosition.getY());
|
||||
double height = 62;
|
||||
double launchAngle = Math.atan2(height, distance);
|
||||
/** DRIVER CONTROLS **/
|
||||
|
||||
if (driverControl.getButton(GamepadKeys.Button.RIGHT_BUMPER) || driverControl.getButton(GamepadKeys.Button.LEFT_BUMPER)) { SPEEDCONTROL = .5; } else { SPEEDCONTROL = 1; }
|
||||
|
||||
// Read pose
|
||||
Pose2d poseEstimate = drive.getLocalizer().getPoseEstimate();
|
||||
|
||||
// Declare a drive direction
|
||||
// Pose representing desired x, y, and angular velocity
|
||||
Pose2d driveDirection = new Pose2d();
|
||||
|
||||
// Standard teleop control
|
||||
// Convert gamepad input into desired pose velocity
|
||||
driveDirection = new Pose2d(
|
||||
-gamepad1.left_stick_y * SPEEDCONTROL,
|
||||
-gamepad1.left_stick_x * SPEEDCONTROL,
|
||||
-gamepad1.right_stick_x * SPEEDCONTROL
|
||||
);
|
||||
|
||||
|
||||
drive.setWeightedDrivePower(driveDirection);
|
||||
|
||||
double headingPose = poseEstimate.getHeading();
|
||||
//9.26
|
||||
|
||||
double totalHeading = ((Math.toRadians((robot.turret.getCurrentPosition() / TICKS_PER_DEGREE))) + headingPose) % (2 * Math.PI);
|
||||
//
|
||||
double theta = Math.atan2(targetPosition.getY() - drive.getPoseEstimate().getY(), targetPosition.getX() - drive.getPoseEstimate().getX()) - drive.getPoseEstimate().getHeading();
|
||||
// theta = Math.atan2(Math.sin(theta), Math.cos(theta));
|
||||
|
||||
boolean withinAngle =
|
||||
Math.abs(theta) < MAX_LOCK_ANGLE;
|
||||
|
||||
// if((realTheta > (2 * Math.PI - 0.5)) || (realTheta < (-2 * Math.PI + 0.5))){
|
||||
// robot.turret.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
||||
// }
|
||||
|
||||
|
||||
controller.setPIDF(p, i, d, f);
|
||||
int armPos = robot.turret.getCurrentPosition();
|
||||
double pid = controller.calculate(armPos, target);
|
||||
|
||||
|
||||
if (withinAngle) {
|
||||
|
||||
int desiredTarget = (int)(TICKS_PER_DEGREE * Math.toDegrees(theta));
|
||||
|
||||
// Clamp to turret mechanical limits
|
||||
desiredTarget = Math.max(
|
||||
TURRET_MIN_TICKS,
|
||||
Math.min(TURRET_MAX_TICKS, desiredTarget)
|
||||
);
|
||||
|
||||
target = desiredTarget;
|
||||
|
||||
controller.setPIDF(p, i, d, f);
|
||||
pid = controller.calculate(armPos, target);
|
||||
|
||||
robot.turret.setPower(pid);
|
||||
|
||||
} else {
|
||||
// Outside lock range → stop or hold
|
||||
robot.turret.setPower(0);
|
||||
}
|
||||
|
||||
telemetry.addData("armPos", armPos);
|
||||
telemetry.addData("target", target);
|
||||
telemetry.addData("pid", pid);
|
||||
telemetry.update();
|
||||
|
||||
driveRIGGED.update();
|
||||
|
||||
if (driverControl.getTrigger(GamepadKeys.Trigger.RIGHT_TRIGGER) > 0.5) {
|
||||
auto = false;
|
||||
robot.intake.set(1);
|
||||
robot.activeTransfer.setPosition(0.85);
|
||||
} else if (driverControl.getTrigger(GamepadKeys.Trigger.LEFT_TRIGGER) > 0.5) {
|
||||
auto = false;
|
||||
robot.intake.set(-1);
|
||||
} else {
|
||||
if (!auto) {
|
||||
robot.intake.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
if(gamepad2.dpad_down) {
|
||||
robot.topFly.set(0);
|
||||
robot.bottomFly.set(0);
|
||||
}
|
||||
|
||||
|
||||
if (gamepad2.dpad_up) {
|
||||
if(!auto) {
|
||||
auto = true;
|
||||
driveRIGGED.followTrajectorySequenceAsync(shootSeq);
|
||||
}
|
||||
}
|
||||
|
||||
if (gamepad2.dpad_right) {
|
||||
driveRIGGED.followTrajectorySequenceAsync(runUp);
|
||||
}
|
||||
|
||||
telemetry.addData("Shooting", shooting[0]);
|
||||
telemetry.addData("RR Busy", drive.isBusy());
|
||||
|
||||
// Update the localizer
|
||||
drive.getLocalizer().update();
|
||||
// Print pose to telemetry
|
||||
telemetry.addData("totalHeading", totalHeading);
|
||||
telemetry.addData("theta", theta);
|
||||
telemetry.addData("x", poseEstimate.getX());
|
||||
telemetry.addData("y", poseEstimate.getY());
|
||||
telemetry.addData("heading", poseEstimate.getHeading());
|
||||
telemetry.addData("auton", auto);
|
||||
telemetry.update();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
while (!isStopRequested() && opModeIsActive());
|
||||
}
|
||||
|
||||
private static void stopAll(Hware robot) {
|
||||
robot.intake.set(0);
|
||||
robot.topFly.set(0);
|
||||
robot.bottomFly.set(0);
|
||||
robot.activeTransfer.setPosition(0.85);
|
||||
}
|
||||
|
||||
private static void cancelTrajectorySequenceCompat(Object drive) {
|
||||
if (invokeIfExists(drive, "cancelTrajectorySequence")) return;
|
||||
if (invokeIfExists(drive, "cancelFollowing")) return;
|
||||
if (invokeIfExists(drive, "breakFollowing")) return;
|
||||
try {
|
||||
Method m = drive.getClass().getMethod("setDrivePower", Pose2d.class);
|
||||
m.invoke(drive, new Pose2d(0, 0, 0));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private static boolean invokeIfExists(Object obj, String methodName) {
|
||||
try {
|
||||
Method m = obj.getClass().getMethod(methodName);
|
||||
m.invoke(obj);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.control.PIDFController;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.geometry.Vector2d;
|
||||
import com.arcrobotics.ftclib.drivebase.MecanumDrive;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadEx;
|
||||
import com.arcrobotics.ftclib.gamepad.GamepadKeys;
|
||||
import com.arcrobotics.ftclib.hardware.motors.Motor;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.Gamepad;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.advanced.PoseStorage;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
|
||||
@TeleOp (name = "Servo")
|
||||
@Config
|
||||
public class ServoTesting extends LinearOpMode {
|
||||
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
|
||||
/** SETUP **/
|
||||
|
||||
// Hardware Map Setup
|
||||
Hware robot = new Hware();
|
||||
robot.initialize(hardwareMap);
|
||||
|
||||
GamepadEx armControl = new GamepadEx(gamepad2);
|
||||
|
||||
|
||||
waitForStart();
|
||||
while (opModeIsActive()) {
|
||||
if (armControl.getTrigger(GamepadKeys.Trigger.RIGHT_TRIGGER) > 0.5) {
|
||||
robot.launchHood.setPosition(0.35);
|
||||
} else if (armControl.getTrigger(GamepadKeys.Trigger.LEFT_TRIGGER) > 0.5) {
|
||||
robot.launchHood.setPosition(0.85);
|
||||
}
|
||||
|
||||
if (armControl.getButton(GamepadKeys.Button.DPAD_UP)) {
|
||||
robot.activeTransfer.setPosition(1);
|
||||
} else if (armControl.getButton(GamepadKeys.Button.DPAD_DOWN)) {
|
||||
robot.activeTransfer.setPosition(0.85);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
while (!isStopRequested() && opModeIsActive());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.firstinspires.ftc.teamcode.drive;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.hardware.PIDFCoefficients;
|
||||
|
||||
/*
|
||||
* Constants shared between multiple drive types.
|
||||
*
|
||||
* TODO: Tune or adjust the following constants to fit your robot. Note that the non-final
|
||||
* fields may also be edited through the dashboard (connect to the robot's WiFi network and
|
||||
* navigate to https://192.168.49.1:8080/dash). Make sure to save the values here after you
|
||||
* adjust them in the dashboard; **config variable changes don't persist between app restarts**.
|
||||
*
|
||||
* These are not the only parameters; some are located in the localizer classes, drive base classes,
|
||||
* and op modes themselves.
|
||||
*/
|
||||
@Config
|
||||
public class DriveConstants {
|
||||
|
||||
/*
|
||||
* These are motor constants that should be listed online for your motors.
|
||||
*/
|
||||
public static final double TICKS_PER_REV = 384.5;
|
||||
public static final double MAX_RPM = 435;
|
||||
|
||||
/*
|
||||
* Set RUN_USING_ENCODER to true to enable built-in hub velocity control using drive encoders.
|
||||
* Set this flag to false if drive encoders are not present and an alternative localization
|
||||
* method is in use (e.g., tracking wheels).
|
||||
*
|
||||
* If using the built-in motor velocity PID, update MOTOR_VELO_PID with the tuned coefficients
|
||||
* from DriveVelocityPIDTuner.
|
||||
*/
|
||||
public static final boolean RUN_USING_ENCODER = false;
|
||||
public static PIDFCoefficients MOTOR_VELO_PID = new PIDFCoefficients(0, 0, 0,
|
||||
getMotorVelocityF(MAX_RPM / 60 * TICKS_PER_REV));
|
||||
|
||||
/*
|
||||
* These are physical constants that can be determined from your robot (including the track
|
||||
* width; it will be tune empirically later although a rough estimate is important). Users are
|
||||
* free to chose whichever linear distance unit they would like so long as it is consistently
|
||||
* used. The default values were selected with inches in mind. Road runner uses radians for
|
||||
* angular distances although most angular parameters are wrapped in Math.toRadians() for
|
||||
* convenience. Make sure to exclude any gear ratio included in MOTOR_CONFIG from GEAR_RATIO.
|
||||
*/
|
||||
public static double WHEEL_RADIUS = 1.8898; // in
|
||||
public static double GEAR_RATIO = (24 / 22); // output (wheel) speed / input (motor) speed
|
||||
public static double TRACK_WIDTH = 12.3; // in
|
||||
|
||||
/*
|
||||
* These are the feedforward parameters used to model the drive motor behavior. If you are using
|
||||
* the built-in velocity PID, *these values are fine as is*. However, if you do not have drive
|
||||
* motor encoders or have elected not to use them for velocity control, these values should be
|
||||
* empirically tuned.
|
||||
*/
|
||||
public static double kV = 1.0 / rpmToVelocity(MAX_RPM);
|
||||
public static double kA = 0;
|
||||
public static double kStatic = 0;
|
||||
|
||||
/*
|
||||
* These values are used to generate the trajectories for you robot. To ensure proper operation,
|
||||
* the constraints should never exceed ~80% of the robot's actual capabilities. While Road
|
||||
* Runner is designed to enable faster autonomous motion, it is a good idea for testing to start
|
||||
* small and gradually increase them later after everything is working. All distance units are
|
||||
* inches.
|
||||
*/
|
||||
public static double MAX_VEL = 60;
|
||||
public static double MAX_ACCEL = 60;
|
||||
public static double MAX_ANG_VEL = Math.toRadians(90);
|
||||
public static double MAX_ANG_ACCEL = Math.toRadians(90);
|
||||
|
||||
/*
|
||||
* Adjust the orientations here to match your robot. See the FTC SDK documentation for details.
|
||||
*/
|
||||
public static RevHubOrientationOnRobot.LogoFacingDirection LOGO_FACING_DIR =
|
||||
RevHubOrientationOnRobot.LogoFacingDirection.RIGHT;
|
||||
public static RevHubOrientationOnRobot.UsbFacingDirection USB_FACING_DIR =
|
||||
RevHubOrientationOnRobot.UsbFacingDirection.UP;
|
||||
|
||||
|
||||
public static double encoderTicksToInches(double ticks) {
|
||||
return WHEEL_RADIUS * 2 * Math.PI * GEAR_RATIO * ticks / TICKS_PER_REV;
|
||||
}
|
||||
|
||||
public static double rpmToVelocity(double rpm) {
|
||||
return rpm * GEAR_RATIO * 2 * Math.PI * WHEEL_RADIUS / 60.0;
|
||||
}
|
||||
|
||||
public static double getMotorVelocityF(double ticksPerSecond) {
|
||||
// see https://docs.google.com/document/d/1tyWrXDfMidwYyP_5H4mZyVgaEswhOC35gvdmP-V-5hA/edit#heading=h.61g9ixenznbx
|
||||
return 32767 / ticksPerSecond;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package org.firstinspires.ftc.teamcode.drive;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.control.PIDCoefficients;
|
||||
import com.acmerobotics.roadrunner.drive.DriveSignal;
|
||||
import com.acmerobotics.roadrunner.drive.MecanumDrive;
|
||||
import com.acmerobotics.roadrunner.followers.HolonomicPIDVAFollower;
|
||||
import com.acmerobotics.roadrunner.followers.TrajectoryFollower;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.AngularVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.MecanumVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.MinVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.ProfileAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryVelocityConstraint;
|
||||
import com.qualcomm.hardware.lynx.LynxModule;
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import com.qualcomm.robotcore.hardware.IMU;
|
||||
import com.qualcomm.robotcore.hardware.PIDFCoefficients;
|
||||
import com.qualcomm.robotcore.hardware.VoltageSensor;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequenceBuilder;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequenceRunner;
|
||||
import org.firstinspires.ftc.teamcode.util.LynxModuleUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ANG_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ANG_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MOTOR_VELO_PID;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.RUN_USING_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.TRACK_WIDTH;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.encoderTicksToInches;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kA;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kStatic;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kV;
|
||||
|
||||
/*
|
||||
* Simple mecanum drive hardware implementation for REV hardware.
|
||||
*/
|
||||
@Config
|
||||
public class SampleMecanumDrive extends MecanumDrive {
|
||||
public static PIDCoefficients TRANSLATIONAL_PID = new PIDCoefficients(0, 0, 0);
|
||||
public static PIDCoefficients HEADING_PID = new PIDCoefficients(0, 0, 0);
|
||||
|
||||
public static double LATERAL_MULTIPLIER = 1;
|
||||
|
||||
public static double VX_WEIGHT = 1;
|
||||
public static double VY_WEIGHT = 1;
|
||||
public static double OMEGA_WEIGHT = 1;
|
||||
|
||||
private TrajectorySequenceRunner trajectorySequenceRunner;
|
||||
|
||||
private static final TrajectoryVelocityConstraint VEL_CONSTRAINT = getVelocityConstraint(MAX_VEL, MAX_ANG_VEL, TRACK_WIDTH);
|
||||
private static final TrajectoryAccelerationConstraint ACCEL_CONSTRAINT = getAccelerationConstraint(MAX_ACCEL);
|
||||
|
||||
private TrajectoryFollower follower;
|
||||
|
||||
private DcMotorEx leftFront, leftRear, rightRear, rightFront;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
private IMU imu;
|
||||
private VoltageSensor batteryVoltageSensor;
|
||||
|
||||
private List<Integer> lastEncPositions = new ArrayList<>();
|
||||
private List<Integer> lastEncVels = new ArrayList<>();
|
||||
|
||||
public SampleMecanumDrive(HardwareMap hardwareMap) {
|
||||
super(kV, kA, kStatic, TRACK_WIDTH, TRACK_WIDTH, LATERAL_MULTIPLIER);
|
||||
|
||||
follower = new HolonomicPIDVAFollower(TRANSLATIONAL_PID, TRANSLATIONAL_PID, HEADING_PID,
|
||||
new Pose2d(0.5, 0.5, Math.toRadians(5.0)), 0.5);
|
||||
|
||||
LynxModuleUtil.ensureMinimumFirmwareVersion(hardwareMap);
|
||||
|
||||
batteryVoltageSensor = hardwareMap.voltageSensor.iterator().next();
|
||||
|
||||
for (LynxModule module : hardwareMap.getAll(LynxModule.class)) {
|
||||
module.setBulkCachingMode(LynxModule.BulkCachingMode.AUTO);
|
||||
}
|
||||
|
||||
// TODO: adjust the names of the following hardware devices to match your configuration
|
||||
// imu = hardwareMap.get(IMU.class, "imu");
|
||||
// IMU.Parameters parameters = new IMU.Parameters(new RevHubOrientationOnRobot(
|
||||
// DriveConstants.LOGO_FACING_DIR, DriveConstants.USB_FACING_DIR));
|
||||
// imu.initialize(parameters);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, "frontLeft");
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, "backLeft");
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, "backRight");
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, "frontRight");
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightRear, rightFront);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
if (RUN_USING_ENCODER) {
|
||||
setMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
||||
}
|
||||
|
||||
setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
|
||||
if (RUN_USING_ENCODER && MOTOR_VELO_PID != null) {
|
||||
setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, MOTOR_VELO_PID);
|
||||
}
|
||||
|
||||
// TODO: reverse any motors using DcMotor.setDirection()
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
List<Integer> lastTrackingEncPositions = new ArrayList<>();
|
||||
List<Integer> lastTrackingEncVels = new ArrayList<>();
|
||||
|
||||
// TODO: if desired, use setLocalizer() to change the localization method
|
||||
setLocalizer(new StandardTrackingWheelLocalizer(hardwareMap, lastTrackingEncPositions, lastTrackingEncVels));
|
||||
|
||||
trajectorySequenceRunner = new TrajectorySequenceRunner(
|
||||
follower, HEADING_PID, batteryVoltageSensor,
|
||||
lastEncPositions, lastEncVels, lastTrackingEncPositions, lastTrackingEncVels
|
||||
);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose) {
|
||||
return new TrajectoryBuilder(startPose, VEL_CONSTRAINT, ACCEL_CONSTRAINT);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose, boolean reversed) {
|
||||
return new TrajectoryBuilder(startPose, reversed, VEL_CONSTRAINT, ACCEL_CONSTRAINT);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose, double startHeading) {
|
||||
return new TrajectoryBuilder(startPose, startHeading, VEL_CONSTRAINT, ACCEL_CONSTRAINT);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder trajectorySequenceBuilder(Pose2d startPose) {
|
||||
return new TrajectorySequenceBuilder(
|
||||
startPose,
|
||||
VEL_CONSTRAINT, ACCEL_CONSTRAINT,
|
||||
MAX_ANG_VEL, MAX_ANG_ACCEL
|
||||
);
|
||||
}
|
||||
|
||||
public void turnAsync(double angle) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(
|
||||
trajectorySequenceBuilder(getPoseEstimate())
|
||||
.turn(angle)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public void turn(double angle) {
|
||||
turnAsync(angle);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public void followTrajectoryAsync(Trajectory trajectory) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(
|
||||
trajectorySequenceBuilder(trajectory.start())
|
||||
.addTrajectory(trajectory)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public void followTrajectory(Trajectory trajectory) {
|
||||
followTrajectoryAsync(trajectory);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public void followTrajectorySequenceAsync(TrajectorySequence trajectorySequence) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(trajectorySequence);
|
||||
}
|
||||
|
||||
public void followTrajectorySequence(TrajectorySequence trajectorySequence) {
|
||||
followTrajectorySequenceAsync(trajectorySequence);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public Pose2d getLastError() {
|
||||
return trajectorySequenceRunner.getLastPoseError();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
updatePoseEstimate();
|
||||
DriveSignal signal = trajectorySequenceRunner.update(getPoseEstimate(), getPoseVelocity());
|
||||
if (signal != null) setDriveSignal(signal);
|
||||
}
|
||||
|
||||
public void waitForIdle() {
|
||||
while (!Thread.currentThread().isInterrupted() && isBusy())
|
||||
update();
|
||||
}
|
||||
|
||||
public boolean isBusy() {
|
||||
return trajectorySequenceRunner.isBusy();
|
||||
}
|
||||
|
||||
public void setMode(DcMotor.RunMode runMode) {
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setMode(runMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZeroPowerBehavior(DcMotor.ZeroPowerBehavior zeroPowerBehavior) {
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(zeroPowerBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPIDFCoefficients(DcMotor.RunMode runMode, PIDFCoefficients coefficients) {
|
||||
PIDFCoefficients compensatedCoefficients = new PIDFCoefficients(
|
||||
coefficients.p, coefficients.i, coefficients.d,
|
||||
coefficients.f * 12 / batteryVoltageSensor.getVoltage()
|
||||
);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setPIDFCoefficients(runMode, compensatedCoefficients);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWeightedDrivePower(Pose2d drivePower) {
|
||||
Pose2d vel = drivePower;
|
||||
|
||||
if (Math.abs(drivePower.getX()) + Math.abs(drivePower.getY())
|
||||
+ Math.abs(drivePower.getHeading()) > 1) {
|
||||
// re-normalize the powers according to the weights
|
||||
double denom = VX_WEIGHT * Math.abs(drivePower.getX())
|
||||
+ VY_WEIGHT * Math.abs(drivePower.getY())
|
||||
+ OMEGA_WEIGHT * Math.abs(drivePower.getHeading());
|
||||
|
||||
vel = new Pose2d(
|
||||
VX_WEIGHT * drivePower.getX(),
|
||||
VY_WEIGHT * drivePower.getY(),
|
||||
OMEGA_WEIGHT * drivePower.getHeading()
|
||||
).div(denom);
|
||||
}
|
||||
|
||||
setDrivePower(vel);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Double> getWheelPositions() {
|
||||
lastEncPositions.clear();
|
||||
|
||||
List<Double> wheelPositions = new ArrayList<>();
|
||||
for (DcMotorEx motor : motors) {
|
||||
int position = motor.getCurrentPosition();
|
||||
lastEncPositions.add(position);
|
||||
wheelPositions.add(encoderTicksToInches(position));
|
||||
}
|
||||
return wheelPositions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Double> getWheelVelocities() {
|
||||
lastEncVels.clear();
|
||||
|
||||
List<Double> wheelVelocities = new ArrayList<>();
|
||||
for (DcMotorEx motor : motors) {
|
||||
int vel = (int) motor.getVelocity();
|
||||
lastEncVels.add(vel);
|
||||
wheelVelocities.add(encoderTicksToInches(vel));
|
||||
}
|
||||
return wheelVelocities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMotorPowers(double v, double v1, double v2, double v3) {
|
||||
leftFront.setPower(v);
|
||||
leftRear.setPower(v1);
|
||||
rightRear.setPower(v2);
|
||||
rightFront.setPower(v3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawExternalHeading() {
|
||||
return imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getExternalHeadingVelocity() {
|
||||
return (double) imu.getRobotAngularVelocity(AngleUnit.RADIANS).zRotationRate;
|
||||
}
|
||||
|
||||
public static TrajectoryVelocityConstraint getVelocityConstraint(double maxVel, double maxAngularVel, double trackWidth) {
|
||||
return new MinVelocityConstraint(Arrays.asList(
|
||||
new AngularVelocityConstraint(maxAngularVel),
|
||||
new MecanumVelocityConstraint(maxVel, trackWidth)
|
||||
));
|
||||
}
|
||||
|
||||
public static TrajectoryAccelerationConstraint getAccelerationConstraint(double maxAccel) {
|
||||
return new ProfileAccelerationConstraint(maxAccel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package org.firstinspires.ftc.teamcode.drive;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ANG_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ANG_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MOTOR_VELO_PID;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.RUN_USING_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.TRACK_WIDTH;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.encoderTicksToInches;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kA;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kStatic;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kV;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.control.PIDCoefficients;
|
||||
import com.acmerobotics.roadrunner.drive.DriveSignal;
|
||||
import com.acmerobotics.roadrunner.drive.MecanumDrive;
|
||||
import com.acmerobotics.roadrunner.followers.HolonomicPIDVAFollower;
|
||||
import com.acmerobotics.roadrunner.followers.TrajectoryFollower;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.AngularVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.MecanumVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.MinVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.ProfileAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryVelocityConstraint;
|
||||
import com.qualcomm.hardware.lynx.LynxModule;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import com.qualcomm.robotcore.hardware.IMU;
|
||||
import com.qualcomm.robotcore.hardware.PIDFCoefficients;
|
||||
import com.qualcomm.robotcore.hardware.VoltageSensor;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequenceBuilder;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequenceRunner;
|
||||
import org.firstinspires.ftc.teamcode.util.LynxModuleUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Simple mecanum drive hardware implementation for REV hardware.
|
||||
*/
|
||||
@Config
|
||||
public class SampleMecanumDriveRIGGED extends MecanumDrive {
|
||||
public static PIDCoefficients TRANSLATIONAL_PID = new PIDCoefficients(0, 0, 0);
|
||||
public static PIDCoefficients HEADING_PID = new PIDCoefficients(0, 0, 0);
|
||||
|
||||
public static double LATERAL_MULTIPLIER = 1;
|
||||
|
||||
public static double VX_WEIGHT = 1;
|
||||
public static double VY_WEIGHT = 1;
|
||||
public static double OMEGA_WEIGHT = 1;
|
||||
|
||||
private TrajectorySequenceRunner trajectorySequenceRunner;
|
||||
|
||||
private static final TrajectoryVelocityConstraint VEL_CONSTRAINT = getVelocityConstraint(MAX_VEL, MAX_ANG_VEL, TRACK_WIDTH);
|
||||
private static final TrajectoryAccelerationConstraint ACCEL_CONSTRAINT = getAccelerationConstraint(MAX_ACCEL);
|
||||
|
||||
private TrajectoryFollower follower;
|
||||
|
||||
private DcMotorEx leftFront, leftRear, rightRear, rightFront;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
private IMU imu;
|
||||
private VoltageSensor batteryVoltageSensor;
|
||||
|
||||
private List<Integer> lastEncPositions = new ArrayList<>();
|
||||
private List<Integer> lastEncVels = new ArrayList<>();
|
||||
|
||||
public SampleMecanumDriveRIGGED(HardwareMap hardwareMap) {
|
||||
super(kV, kA, kStatic, TRACK_WIDTH, TRACK_WIDTH, LATERAL_MULTIPLIER);
|
||||
|
||||
follower = new HolonomicPIDVAFollower(TRANSLATIONAL_PID, TRANSLATIONAL_PID, HEADING_PID,
|
||||
new Pose2d(0.5, 0.5, Math.toRadians(5.0)), 0.5);
|
||||
|
||||
LynxModuleUtil.ensureMinimumFirmwareVersion(hardwareMap);
|
||||
|
||||
batteryVoltageSensor = hardwareMap.voltageSensor.iterator().next();
|
||||
|
||||
for (LynxModule module : hardwareMap.getAll(LynxModule.class)) {
|
||||
module.setBulkCachingMode(LynxModule.BulkCachingMode.AUTO);
|
||||
}
|
||||
|
||||
// TODO: adjust the names of the following hardware devices to match your configuration
|
||||
// imu = hardwareMap.get(IMU.class, "imu");
|
||||
// IMU.Parameters parameters = new IMU.Parameters(new RevHubOrientationOnRobot(
|
||||
// DriveConstants.LOGO_FACING_DIR, DriveConstants.USB_FACING_DIR));
|
||||
// imu.initialize(parameters);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, "frontLeft");
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, "backLeft");
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, "backRight");
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, "frontRight");
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightRear, rightFront);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
if (RUN_USING_ENCODER) {
|
||||
setMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
||||
}
|
||||
|
||||
setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
|
||||
if (RUN_USING_ENCODER && MOTOR_VELO_PID != null) {
|
||||
setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, MOTOR_VELO_PID);
|
||||
}
|
||||
|
||||
// TODO: reverse any motors using DcMotor.setDirection()
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
List<Integer> lastTrackingEncPositions = new ArrayList<>();
|
||||
List<Integer> lastTrackingEncVels = new ArrayList<>();
|
||||
|
||||
// TODO: if desired, use setLocalizer() to change the localization method
|
||||
setLocalizer(new StandardTrackingWheelLocalizer(hardwareMap, lastTrackingEncPositions, lastTrackingEncVels));
|
||||
|
||||
trajectorySequenceRunner = new TrajectorySequenceRunner(
|
||||
follower, HEADING_PID, batteryVoltageSensor,
|
||||
lastEncPositions, lastEncVels, lastTrackingEncPositions, lastTrackingEncVels
|
||||
);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose) {
|
||||
return new TrajectoryBuilder(startPose, VEL_CONSTRAINT, ACCEL_CONSTRAINT);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose, boolean reversed) {
|
||||
return new TrajectoryBuilder(startPose, reversed, VEL_CONSTRAINT, ACCEL_CONSTRAINT);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose, double startHeading) {
|
||||
return new TrajectoryBuilder(startPose, startHeading, VEL_CONSTRAINT, ACCEL_CONSTRAINT);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder trajectorySequenceBuilder(Pose2d startPose) {
|
||||
return new TrajectorySequenceBuilder(
|
||||
startPose,
|
||||
VEL_CONSTRAINT, ACCEL_CONSTRAINT,
|
||||
MAX_ANG_VEL, MAX_ANG_ACCEL
|
||||
);
|
||||
}
|
||||
|
||||
public void turnAsync(double angle) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(
|
||||
trajectorySequenceBuilder(getPoseEstimate())
|
||||
.turn(angle)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public void turn(double angle) {
|
||||
turnAsync(angle);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public void followTrajectoryAsync(Trajectory trajectory) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(
|
||||
trajectorySequenceBuilder(trajectory.start())
|
||||
.addTrajectory(trajectory)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public void followTrajectory(Trajectory trajectory) {
|
||||
followTrajectoryAsync(trajectory);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public void followTrajectorySequenceAsync(TrajectorySequence trajectorySequence) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(trajectorySequence);
|
||||
}
|
||||
|
||||
public void followTrajectorySequence(TrajectorySequence trajectorySequence) {
|
||||
followTrajectorySequenceAsync(trajectorySequence);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public Pose2d getLastError() {
|
||||
return trajectorySequenceRunner.getLastPoseError();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
updatePoseEstimate();
|
||||
DriveSignal signal = trajectorySequenceRunner.update(getPoseEstimate(), getPoseVelocity());
|
||||
if (signal != null) setDriveSignal(signal);
|
||||
}
|
||||
|
||||
public void waitForIdle() {
|
||||
while (!Thread.currentThread().isInterrupted() && isBusy())
|
||||
update();
|
||||
}
|
||||
|
||||
public boolean isBusy() {
|
||||
return trajectorySequenceRunner.isBusy();
|
||||
}
|
||||
|
||||
public void setMode(DcMotor.RunMode runMode) {
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setMode(runMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZeroPowerBehavior(DcMotor.ZeroPowerBehavior zeroPowerBehavior) {
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(zeroPowerBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPIDFCoefficients(DcMotor.RunMode runMode, PIDFCoefficients coefficients) {
|
||||
PIDFCoefficients compensatedCoefficients = new PIDFCoefficients(
|
||||
coefficients.p, coefficients.i, coefficients.d,
|
||||
coefficients.f * 12 / batteryVoltageSensor.getVoltage()
|
||||
);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setPIDFCoefficients(runMode, compensatedCoefficients);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWeightedDrivePower(Pose2d drivePower) {
|
||||
Pose2d vel = drivePower;
|
||||
|
||||
if (Math.abs(drivePower.getX()) + Math.abs(drivePower.getY())
|
||||
+ Math.abs(drivePower.getHeading()) > 1) {
|
||||
// re-normalize the powers according to the weights
|
||||
double denom = VX_WEIGHT * Math.abs(drivePower.getX())
|
||||
+ VY_WEIGHT * Math.abs(drivePower.getY())
|
||||
+ OMEGA_WEIGHT * Math.abs(drivePower.getHeading());
|
||||
|
||||
vel = new Pose2d(
|
||||
VX_WEIGHT * drivePower.getX(),
|
||||
VY_WEIGHT * drivePower.getY(),
|
||||
OMEGA_WEIGHT * drivePower.getHeading()
|
||||
).div(denom);
|
||||
}
|
||||
|
||||
setDrivePower(vel);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Double> getWheelPositions() {
|
||||
lastEncPositions.clear();
|
||||
|
||||
List<Double> wheelPositions = new ArrayList<>();
|
||||
for (DcMotorEx motor : motors) {
|
||||
int position = motor.getCurrentPosition();
|
||||
lastEncPositions.add(position);
|
||||
wheelPositions.add(encoderTicksToInches(position));
|
||||
}
|
||||
return wheelPositions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Double> getWheelVelocities() {
|
||||
lastEncVels.clear();
|
||||
|
||||
List<Double> wheelVelocities = new ArrayList<>();
|
||||
for (DcMotorEx motor : motors) {
|
||||
int vel = (int) motor.getVelocity();
|
||||
lastEncVels.add(vel);
|
||||
wheelVelocities.add(encoderTicksToInches(vel));
|
||||
}
|
||||
return wheelVelocities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMotorPowers(double v, double v1, double v2, double v3) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawExternalHeading() {
|
||||
return imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getExternalHeadingVelocity() {
|
||||
return (double) imu.getRobotAngularVelocity(AngleUnit.RADIANS).zRotationRate;
|
||||
}
|
||||
|
||||
public static TrajectoryVelocityConstraint getVelocityConstraint(double maxVel, double maxAngularVel, double trackWidth) {
|
||||
return new MinVelocityConstraint(Arrays.asList(
|
||||
new AngularVelocityConstraint(maxAngularVel),
|
||||
new MecanumVelocityConstraint(maxVel, trackWidth)
|
||||
));
|
||||
}
|
||||
|
||||
public static TrajectoryAccelerationConstraint getAccelerationConstraint(double maxAccel) {
|
||||
return new ProfileAccelerationConstraint(maxAccel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package org.firstinspires.ftc.teamcode.drive;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.control.PIDCoefficients;
|
||||
import com.acmerobotics.roadrunner.drive.DriveSignal;
|
||||
import com.acmerobotics.roadrunner.drive.TankDrive;
|
||||
import com.acmerobotics.roadrunner.followers.TankPIDVAFollower;
|
||||
import com.acmerobotics.roadrunner.followers.TrajectoryFollower;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.AngularVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.MinVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.ProfileAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TankVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryVelocityConstraint;
|
||||
import com.qualcomm.hardware.lynx.LynxModule;
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import com.qualcomm.robotcore.hardware.IMU;
|
||||
import com.qualcomm.robotcore.hardware.PIDFCoefficients;
|
||||
import com.qualcomm.robotcore.hardware.VoltageSensor;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequenceBuilder;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequenceRunner;
|
||||
import org.firstinspires.ftc.teamcode.util.LynxModuleUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ANG_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ANG_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MOTOR_VELO_PID;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.RUN_USING_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.TRACK_WIDTH;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.encoderTicksToInches;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kA;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kStatic;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kV;
|
||||
|
||||
/*
|
||||
* Simple tank drive hardware implementation for REV hardware.
|
||||
*/
|
||||
@Config
|
||||
public class SampleTankDrive extends TankDrive {
|
||||
public static PIDCoefficients AXIAL_PID = new PIDCoefficients(0, 0, 0);
|
||||
public static PIDCoefficients CROSS_TRACK_PID = new PIDCoefficients(0, 0, 0);
|
||||
public static PIDCoefficients HEADING_PID = new PIDCoefficients(0, 0, 0);
|
||||
|
||||
public static double VX_WEIGHT = 1;
|
||||
public static double OMEGA_WEIGHT = 1;
|
||||
|
||||
private TrajectorySequenceRunner trajectorySequenceRunner;
|
||||
|
||||
private static final TrajectoryVelocityConstraint VEL_CONSTRAINT = getVelocityConstraint(MAX_VEL, MAX_ANG_VEL, TRACK_WIDTH);
|
||||
private static final TrajectoryAccelerationConstraint accelConstraint = getAccelerationConstraint(MAX_ACCEL);
|
||||
|
||||
private TrajectoryFollower follower;
|
||||
|
||||
private List<DcMotorEx> motors, leftMotors, rightMotors;
|
||||
private IMU imu;
|
||||
|
||||
private VoltageSensor batteryVoltageSensor;
|
||||
|
||||
public SampleTankDrive(HardwareMap hardwareMap) {
|
||||
super(kV, kA, kStatic, TRACK_WIDTH);
|
||||
|
||||
follower = new TankPIDVAFollower(AXIAL_PID, CROSS_TRACK_PID,
|
||||
new Pose2d(0.5, 0.5, Math.toRadians(5.0)), 0.5);
|
||||
|
||||
LynxModuleUtil.ensureMinimumFirmwareVersion(hardwareMap);
|
||||
|
||||
batteryVoltageSensor = hardwareMap.voltageSensor.iterator().next();
|
||||
|
||||
for (LynxModule module : hardwareMap.getAll(LynxModule.class)) {
|
||||
module.setBulkCachingMode(LynxModule.BulkCachingMode.AUTO);
|
||||
}
|
||||
|
||||
// TODO: adjust the names of the following hardware devices to match your configuration
|
||||
imu = hardwareMap.get(IMU.class, "imu");
|
||||
IMU.Parameters parameters = new IMU.Parameters(new RevHubOrientationOnRobot(
|
||||
DriveConstants.LOGO_FACING_DIR, DriveConstants.USB_FACING_DIR));
|
||||
imu.initialize(parameters);
|
||||
|
||||
// add/remove motors depending on your robot (e.g., 6WD)
|
||||
DcMotorEx leftFront = hardwareMap.get(DcMotorEx.class, "leftFront");
|
||||
DcMotorEx leftRear = hardwareMap.get(DcMotorEx.class, "leftRear");
|
||||
DcMotorEx rightRear = hardwareMap.get(DcMotorEx.class, "rightRear");
|
||||
DcMotorEx rightFront = hardwareMap.get(DcMotorEx.class, "rightFront");
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightRear, rightFront);
|
||||
leftMotors = Arrays.asList(leftFront, leftRear);
|
||||
rightMotors = Arrays.asList(rightFront, rightRear);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
if (RUN_USING_ENCODER) {
|
||||
setMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
||||
}
|
||||
|
||||
setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
|
||||
if (RUN_USING_ENCODER && MOTOR_VELO_PID != null) {
|
||||
setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, MOTOR_VELO_PID);
|
||||
}
|
||||
|
||||
// TODO: reverse any motors using DcMotor.setDirection()
|
||||
|
||||
// TODO: if desired, use setLocalizer() to change the localization method
|
||||
// for instance, setLocalizer(new ThreeTrackingWheelLocalizer(...));
|
||||
|
||||
trajectorySequenceRunner = new TrajectorySequenceRunner(
|
||||
follower, HEADING_PID, batteryVoltageSensor,
|
||||
new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()
|
||||
);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose) {
|
||||
return new TrajectoryBuilder(startPose, VEL_CONSTRAINT, accelConstraint);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose, boolean reversed) {
|
||||
return new TrajectoryBuilder(startPose, reversed, VEL_CONSTRAINT, accelConstraint);
|
||||
}
|
||||
|
||||
public TrajectoryBuilder trajectoryBuilder(Pose2d startPose, double startHeading) {
|
||||
return new TrajectoryBuilder(startPose, startHeading, VEL_CONSTRAINT, accelConstraint);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder trajectorySequenceBuilder(Pose2d startPose) {
|
||||
return new TrajectorySequenceBuilder(
|
||||
startPose,
|
||||
VEL_CONSTRAINT, accelConstraint,
|
||||
MAX_ANG_VEL, MAX_ANG_ACCEL
|
||||
);
|
||||
}
|
||||
|
||||
public void turnAsync(double angle) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(
|
||||
trajectorySequenceBuilder(getPoseEstimate())
|
||||
.turn(angle)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public void turn(double angle) {
|
||||
turnAsync(angle);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public void followTrajectoryAsync(Trajectory trajectory) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(
|
||||
trajectorySequenceBuilder(trajectory.start())
|
||||
.addTrajectory(trajectory)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public void followTrajectory(Trajectory trajectory) {
|
||||
followTrajectoryAsync(trajectory);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public void followTrajectorySequenceAsync(TrajectorySequence trajectorySequence) {
|
||||
trajectorySequenceRunner.followTrajectorySequenceAsync(trajectorySequence);
|
||||
}
|
||||
|
||||
public void followTrajectorySequence(TrajectorySequence trajectorySequence) {
|
||||
followTrajectorySequenceAsync(trajectorySequence);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
public Pose2d getLastError() {
|
||||
return trajectorySequenceRunner.getLastPoseError();
|
||||
}
|
||||
|
||||
|
||||
public void update() {
|
||||
updatePoseEstimate();
|
||||
DriveSignal signal = trajectorySequenceRunner.update(getPoseEstimate(), getPoseVelocity());
|
||||
if (signal != null) setDriveSignal(signal);
|
||||
}
|
||||
|
||||
public void waitForIdle() {
|
||||
while (!Thread.currentThread().isInterrupted() && isBusy())
|
||||
update();
|
||||
}
|
||||
|
||||
public boolean isBusy() {
|
||||
return trajectorySequenceRunner.isBusy();
|
||||
}
|
||||
|
||||
public void setMode(DcMotor.RunMode runMode) {
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setMode(runMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZeroPowerBehavior(DcMotor.ZeroPowerBehavior zeroPowerBehavior) {
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(zeroPowerBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPIDFCoefficients(DcMotor.RunMode runMode, PIDFCoefficients coefficients) {
|
||||
PIDFCoefficients compensatedCoefficients = new PIDFCoefficients(
|
||||
coefficients.p, coefficients.i, coefficients.d,
|
||||
coefficients.f * 12 / batteryVoltageSensor.getVoltage()
|
||||
);
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setPIDFCoefficients(runMode, compensatedCoefficients);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWeightedDrivePower(Pose2d drivePower) {
|
||||
Pose2d vel = drivePower;
|
||||
|
||||
if (Math.abs(drivePower.getX()) + Math.abs(drivePower.getHeading()) > 1) {
|
||||
// re-normalize the powers according to the weights
|
||||
double denom = VX_WEIGHT * Math.abs(drivePower.getX())
|
||||
+ OMEGA_WEIGHT * Math.abs(drivePower.getHeading());
|
||||
|
||||
vel = new Pose2d(
|
||||
VX_WEIGHT * drivePower.getX(),
|
||||
0,
|
||||
OMEGA_WEIGHT * drivePower.getHeading()
|
||||
).div(denom);
|
||||
} else {
|
||||
// Ensure the y axis is zeroed out.
|
||||
vel = new Pose2d(drivePower.getX(), 0, drivePower.getHeading());
|
||||
}
|
||||
|
||||
setDrivePower(vel);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Double> getWheelPositions() {
|
||||
double leftSum = 0, rightSum = 0;
|
||||
for (DcMotorEx leftMotor : leftMotors) {
|
||||
leftSum += encoderTicksToInches(leftMotor.getCurrentPosition());
|
||||
}
|
||||
for (DcMotorEx rightMotor : rightMotors) {
|
||||
rightSum += encoderTicksToInches(rightMotor.getCurrentPosition());
|
||||
}
|
||||
return Arrays.asList(leftSum / leftMotors.size(), rightSum / rightMotors.size());
|
||||
}
|
||||
|
||||
public List<Double> getWheelVelocities() {
|
||||
double leftSum = 0, rightSum = 0;
|
||||
for (DcMotorEx leftMotor : leftMotors) {
|
||||
leftSum += encoderTicksToInches(leftMotor.getVelocity());
|
||||
}
|
||||
for (DcMotorEx rightMotor : rightMotors) {
|
||||
rightSum += encoderTicksToInches(rightMotor.getVelocity());
|
||||
}
|
||||
return Arrays.asList(leftSum / leftMotors.size(), rightSum / rightMotors.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMotorPowers(double v, double v1) {
|
||||
for (DcMotorEx leftMotor : leftMotors) {
|
||||
leftMotor.setPower(v);
|
||||
}
|
||||
for (DcMotorEx rightMotor : rightMotors) {
|
||||
rightMotor.setPower(v1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getRawExternalHeading() {
|
||||
return imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getExternalHeadingVelocity() {
|
||||
return (double) imu.getRobotAngularVelocity(AngleUnit.RADIANS).zRotationRate;
|
||||
}
|
||||
|
||||
public static TrajectoryVelocityConstraint getVelocityConstraint(double maxVel, double maxAngularVel, double trackWidth) {
|
||||
return new MinVelocityConstraint(Arrays.asList(
|
||||
new AngularVelocityConstraint(maxAngularVel),
|
||||
new TankVelocityConstraint(maxVel, trackWidth)
|
||||
));
|
||||
}
|
||||
|
||||
public static TrajectoryAccelerationConstraint getAccelerationConstraint(double maxAccel) {
|
||||
return new ProfileAccelerationConstraint(maxAccel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.firstinspires.ftc.teamcode.drive;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.localization.ThreeTrackingWheelLocalizer;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import org.firstinspires.ftc.teamcode.util.Encoder;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Sample tracking wheel localizer implementation assuming the standard configuration:
|
||||
*
|
||||
* /--------------\
|
||||
* | ____ |
|
||||
* | ---- |
|
||||
* | || || |
|
||||
* | || || |
|
||||
* | |
|
||||
* | |
|
||||
* \--------------/
|
||||
*
|
||||
*/
|
||||
@Config
|
||||
public class StandardTrackingWheelLocalizer extends ThreeTrackingWheelLocalizer {
|
||||
public static double TICKS_PER_REV = 2000;
|
||||
public static double WHEEL_RADIUS = 0.944882; // in
|
||||
public static double GEAR_RATIO = 1; // output (wheel) speed / input (encoder) speed
|
||||
|
||||
public static double LATERAL_DISTANCE = 6.2; // in; distance between the left and right wheels
|
||||
public static double FORWARD_OFFSET = -6; // in; offset of the lateral wheel
|
||||
|
||||
private Encoder leftEncoder, rightEncoder, frontEncoder;
|
||||
|
||||
private List<Integer> lastEncPositions, lastEncVels;
|
||||
|
||||
|
||||
public static double X_MULTIPLIER = 1; // Multiplier in the X direction
|
||||
public static double Y_MULTIPLIER = 1; // Multiplier in the Y direction
|
||||
|
||||
public StandardTrackingWheelLocalizer(HardwareMap hardwareMap, List<Integer> lastTrackingEncPositions, List<Integer> lastTrackingEncVels) {
|
||||
super(Arrays.asList(
|
||||
new Pose2d(0, LATERAL_DISTANCE / 2, 0), // left
|
||||
new Pose2d(0, -LATERAL_DISTANCE / 2, 0), // right
|
||||
new Pose2d(FORWARD_OFFSET, 0, Math.toRadians(90)) // front
|
||||
));
|
||||
|
||||
lastEncPositions = lastTrackingEncPositions;
|
||||
lastEncVels = lastTrackingEncVels;
|
||||
|
||||
leftEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, "intake"));
|
||||
rightEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, "topFly"));
|
||||
frontEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, "bottomFly"));
|
||||
|
||||
// TODO: reverse any encoders using Encoder.setDirection(Encoder.Direction.REVERSE)
|
||||
// leftEncoder.setDirection(Encoder.Direction.REVERSE);
|
||||
}
|
||||
|
||||
public static double encoderTicksToInches(double ticks) {
|
||||
return WHEEL_RADIUS * 2 * Math.PI * GEAR_RATIO * ticks / TICKS_PER_REV;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Double> getWheelPositions() {
|
||||
int leftPos = leftEncoder.getCurrentPosition();
|
||||
int rightPos = rightEncoder.getCurrentPosition();
|
||||
int frontPos = frontEncoder.getCurrentPosition();
|
||||
|
||||
lastEncPositions.clear();
|
||||
lastEncPositions.add(leftPos);
|
||||
lastEncPositions.add(rightPos);
|
||||
lastEncPositions.add(frontPos);
|
||||
|
||||
return Arrays.asList(
|
||||
encoderTicksToInches(leftPos),
|
||||
encoderTicksToInches(rightPos),
|
||||
encoderTicksToInches(frontPos)
|
||||
);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Double> getWheelVelocities() {
|
||||
int leftVel = (int) leftEncoder.getCorrectedVelocity();
|
||||
int rightVel = (int) rightEncoder.getCorrectedVelocity();
|
||||
int frontVel = (int) frontEncoder.getCorrectedVelocity();
|
||||
|
||||
lastEncVels.clear();
|
||||
lastEncVels.add(leftVel);
|
||||
lastEncVels.add(rightVel);
|
||||
lastEncVels.add(frontVel);
|
||||
|
||||
return Arrays.asList(
|
||||
encoderTicksToInches(leftVel) * X_MULTIPLIER,
|
||||
encoderTicksToInches(rightVel) * X_MULTIPLIER,
|
||||
encoderTicksToInches(frontVel) * Y_MULTIPLIER
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.advanced;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
|
||||
/**
|
||||
* Simple static field serving as a storage medium for the bot's pose.
|
||||
* This allows different classes/opmodes to set and read from a central source of truth.
|
||||
* A static field allows data to persist between opmodes.
|
||||
*/
|
||||
public class PoseStorage {
|
||||
public static Pose2d currentPose = new Pose2d(0,0,0);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_RPM;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.RUN_USING_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.rpmToVelocity;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.util.NanoClock;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.util.RobotLog;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.robotcore.internal.system.Misc;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.util.LoggingUtil;
|
||||
import org.firstinspires.ftc.teamcode.util.RegressionUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Op mode for computing kV, kStatic, and kA from various drive routines. For the curious, here's an
|
||||
* outline of the procedure:
|
||||
* 1. Slowly ramp the motor power and record encoder values along the way.
|
||||
* 2. Run a linear regression on the encoder velocity vs. motor power plot to obtain a slope (kV)
|
||||
* and an optional intercept (kStatic).
|
||||
* 3. Accelerate the robot (apply constant power) and record the encoder counts.
|
||||
* 4. Adjust the encoder data based on the velocity tuning data and find kA with another linear
|
||||
* regression.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class AutomaticFeedforwardTuner extends LinearOpMode {
|
||||
public static double MAX_POWER = 0.7;
|
||||
public static double DISTANCE = 40; // in
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
if (RUN_USING_ENCODER) {
|
||||
RobotLog.setGlobalErrorMsg("Feedforward constants usually don't need to be tuned " +
|
||||
"when using the built-in drive motor velocity PID.");
|
||||
}
|
||||
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
NanoClock clock = NanoClock.system();
|
||||
|
||||
telemetry.addLine("Press play to begin the feedforward tuning routine");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Would you like to fit kStatic?");
|
||||
telemetry.addLine("Press (Y/Δ) for yes, (B/O) for no");
|
||||
telemetry.update();
|
||||
|
||||
boolean fitIntercept = false;
|
||||
while (!isStopRequested()) {
|
||||
if (gamepad1.y) {
|
||||
fitIntercept = true;
|
||||
while (!isStopRequested() && gamepad1.y) {
|
||||
idle();
|
||||
}
|
||||
break;
|
||||
} else if (gamepad1.b) {
|
||||
while (!isStopRequested() && gamepad1.b) {
|
||||
idle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
idle();
|
||||
}
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine(Misc.formatInvariant(
|
||||
"Place your robot on the field with at least %.2f in of room in front", DISTANCE));
|
||||
telemetry.addLine("Press (Y/Δ) to begin");
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested() && !gamepad1.y) {
|
||||
idle();
|
||||
}
|
||||
while (!isStopRequested() && gamepad1.y) {
|
||||
idle();
|
||||
}
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Running...");
|
||||
telemetry.update();
|
||||
|
||||
double maxVel = rpmToVelocity(MAX_RPM);
|
||||
double finalVel = MAX_POWER * maxVel;
|
||||
double accel = (finalVel * finalVel) / (2.0 * DISTANCE);
|
||||
double rampTime = Math.sqrt(2.0 * DISTANCE / accel);
|
||||
|
||||
List<Double> timeSamples = new ArrayList<>();
|
||||
List<Double> positionSamples = new ArrayList<>();
|
||||
List<Double> powerSamples = new ArrayList<>();
|
||||
|
||||
drive.setPoseEstimate(new Pose2d());
|
||||
|
||||
double startTime = clock.seconds();
|
||||
while (!isStopRequested()) {
|
||||
double elapsedTime = clock.seconds() - startTime;
|
||||
if (elapsedTime > rampTime) {
|
||||
break;
|
||||
}
|
||||
double vel = accel * elapsedTime;
|
||||
double power = vel / maxVel;
|
||||
|
||||
timeSamples.add(elapsedTime);
|
||||
positionSamples.add(drive.getPoseEstimate().getX());
|
||||
powerSamples.add(power);
|
||||
|
||||
drive.setDrivePower(new Pose2d(power, 0.0, 0.0));
|
||||
drive.updatePoseEstimate();
|
||||
}
|
||||
drive.setDrivePower(new Pose2d(0.0, 0.0, 0.0));
|
||||
|
||||
RegressionUtil.RampResult rampResult = RegressionUtil.fitRampData(
|
||||
timeSamples, positionSamples, powerSamples, fitIntercept,
|
||||
LoggingUtil.getLogFile(Misc.formatInvariant(
|
||||
"DriveRampRegression-%d.csv", System.currentTimeMillis())));
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Quasi-static ramp up test complete");
|
||||
if (fitIntercept) {
|
||||
telemetry.addLine(Misc.formatInvariant("kV = %.5f, kStatic = %.5f (R^2 = %.2f)",
|
||||
rampResult.kV, rampResult.kStatic, rampResult.rSquare));
|
||||
} else {
|
||||
telemetry.addLine(Misc.formatInvariant("kV = %.5f (R^2 = %.2f)",
|
||||
rampResult.kStatic, rampResult.rSquare));
|
||||
}
|
||||
telemetry.addLine("Would you like to fit kA?");
|
||||
telemetry.addLine("Press (Y/Δ) for yes, (B/O) for no");
|
||||
telemetry.update();
|
||||
|
||||
boolean fitAccelFF = false;
|
||||
while (!isStopRequested()) {
|
||||
if (gamepad1.y) {
|
||||
fitAccelFF = true;
|
||||
while (!isStopRequested() && gamepad1.y) {
|
||||
idle();
|
||||
}
|
||||
break;
|
||||
} else if (gamepad1.b) {
|
||||
while (!isStopRequested() && gamepad1.b) {
|
||||
idle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
idle();
|
||||
}
|
||||
|
||||
if (fitAccelFF) {
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Place the robot back in its starting position");
|
||||
telemetry.addLine("Press (Y/Δ) to continue");
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested() && !gamepad1.y) {
|
||||
idle();
|
||||
}
|
||||
while (!isStopRequested() && gamepad1.y) {
|
||||
idle();
|
||||
}
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Running...");
|
||||
telemetry.update();
|
||||
|
||||
double maxPowerTime = DISTANCE / maxVel;
|
||||
|
||||
timeSamples.clear();
|
||||
positionSamples.clear();
|
||||
powerSamples.clear();
|
||||
|
||||
drive.setPoseEstimate(new Pose2d());
|
||||
drive.setDrivePower(new Pose2d(MAX_POWER, 0.0, 0.0));
|
||||
|
||||
startTime = clock.seconds();
|
||||
while (!isStopRequested()) {
|
||||
double elapsedTime = clock.seconds() - startTime;
|
||||
if (elapsedTime > maxPowerTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
timeSamples.add(elapsedTime);
|
||||
positionSamples.add(drive.getPoseEstimate().getX());
|
||||
powerSamples.add(MAX_POWER);
|
||||
|
||||
drive.updatePoseEstimate();
|
||||
}
|
||||
drive.setDrivePower(new Pose2d(0.0, 0.0, 0.0));
|
||||
|
||||
RegressionUtil.AccelResult accelResult = RegressionUtil.fitAccelData(
|
||||
timeSamples, positionSamples, powerSamples, rampResult,
|
||||
LoggingUtil.getLogFile(Misc.formatInvariant(
|
||||
"DriveAccelRegression-%d.csv", System.currentTimeMillis())));
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Constant power test complete");
|
||||
telemetry.addLine(Misc.formatInvariant("kA = %.5f (R^2 = %.2f)",
|
||||
accelResult.kA, accelResult.rSquare));
|
||||
telemetry.update();
|
||||
}
|
||||
|
||||
while (!isStopRequested()) {
|
||||
idle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/*
|
||||
* Op mode for preliminary tuning of the follower PID coefficients (located in the drive base
|
||||
* classes). The robot drives back and forth in a straight line indefinitely. Utilization of the
|
||||
* dashboard is recommended for this tuning routine. To access the dashboard, connect your computer
|
||||
* to the RC's WiFi network. In your browser, navigate to https://192.168.49.1:8080/dash if you're
|
||||
* using the RC phone or https://192.168.43.1:8080/dash if you are using the Control Hub. Once
|
||||
* you've successfully connected, start the program, and your robot will begin moving forward and
|
||||
* backward. You should observe the target position (green) and your pose estimate (blue) and adjust
|
||||
* your follower PID coefficients such that you follow the target position as accurately as possible.
|
||||
* If you are using SampleMecanumDrive, you should be tuning TRANSLATIONAL_PID and HEADING_PID.
|
||||
* If you are using SampleTankDrive, you should be tuning AXIAL_PID, CROSS_TRACK_PID, and HEADING_PID.
|
||||
* These coefficients can be tuned live in dashboard.
|
||||
*
|
||||
* This opmode is designed as a convenient, coarse tuning for the follower PID coefficients. It
|
||||
* is recommended that you use the FollowerPIDTuner opmode for further fine tuning.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class BackAndForth extends LinearOpMode {
|
||||
|
||||
public static double DISTANCE = 50;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
Trajectory trajectoryForward = drive.trajectoryBuilder(new Pose2d())
|
||||
.forward(DISTANCE)
|
||||
.build();
|
||||
|
||||
Trajectory trajectoryBackward = drive.trajectoryBuilder(trajectoryForward.end())
|
||||
.back(DISTANCE)
|
||||
.build();
|
||||
|
||||
waitForStart();
|
||||
|
||||
while (opModeIsActive() && !isStopRequested()) {
|
||||
drive.followTrajectory(trajectoryForward);
|
||||
drive.followTrajectory(trajectoryBackward);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MOTOR_VELO_PID;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.RUN_USING_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kV;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfile;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfileGenerator;
|
||||
import com.acmerobotics.roadrunner.profile.MotionState;
|
||||
import com.acmerobotics.roadrunner.util.NanoClock;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.util.RobotLog;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This routine is designed to tune the PID coefficients used by the REV Expansion Hubs for closed-
|
||||
* loop velocity control. Although it may seem unnecessary, tuning these coefficients is just as
|
||||
* important as the positional parameters. Like the other manual tuning routines, this op mode
|
||||
* relies heavily upon the dashboard. To access the dashboard, connect your computer to the RC's
|
||||
* WiFi network. In your browser, navigate to https://192.168.49.1:8080/dash if you're using the RC
|
||||
* phone or https://192.168.43.1:8080/dash if you are using the Control Hub. Once you've successfully
|
||||
* connected, start the program, and your robot will begin moving forward and backward according to
|
||||
* a motion profile. Your job is to graph the velocity errors over time and adjust the PID
|
||||
* coefficients (note: the tuning variable will not appear until the op mode finishes initializing).
|
||||
* Once you've found a satisfactory set of gains, add them to the DriveConstants.java file under the
|
||||
* MOTOR_VELO_PID field.
|
||||
*
|
||||
* Recommended tuning process:
|
||||
*
|
||||
* 1. Increase kP until any phase lag is eliminated. Concurrently increase kD as necessary to
|
||||
* mitigate oscillations.
|
||||
* 2. Add kI (or adjust kF) until the steady state/constant velocity plateaus are reached.
|
||||
* 3. Back off kP and kD a little until the response is less oscillatory (but without lag).
|
||||
*
|
||||
* Pressing Y/Δ (Xbox/PS4) will pause the tuning process and enter driver override, allowing the
|
||||
* user to reset the position of the bot in the event that it drifts off the path.
|
||||
* Pressing B/O (Xbox/PS4) will cede control back to the tuning process.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class DriveVelocityPIDTuner extends LinearOpMode {
|
||||
public static double DISTANCE = 72; // in
|
||||
|
||||
enum Mode {
|
||||
DRIVER_MODE,
|
||||
TUNING_MODE
|
||||
}
|
||||
|
||||
private static MotionProfile generateProfile(boolean movingForward) {
|
||||
MotionState start = new MotionState(movingForward ? 0 : DISTANCE, 0, 0, 0);
|
||||
MotionState goal = new MotionState(movingForward ? DISTANCE : 0, 0, 0, 0);
|
||||
return MotionProfileGenerator.generateSimpleMotionProfile(start, goal, MAX_VEL, MAX_ACCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
if (!RUN_USING_ENCODER) {
|
||||
RobotLog.setGlobalErrorMsg("%s does not need to be run if the built-in motor velocity" +
|
||||
"PID is not in use", getClass().getSimpleName());
|
||||
}
|
||||
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
Mode mode = Mode.TUNING_MODE;
|
||||
|
||||
double lastKp = MOTOR_VELO_PID.p;
|
||||
double lastKi = MOTOR_VELO_PID.i;
|
||||
double lastKd = MOTOR_VELO_PID.d;
|
||||
double lastKf = MOTOR_VELO_PID.f;
|
||||
|
||||
drive.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, MOTOR_VELO_PID);
|
||||
|
||||
NanoClock clock = NanoClock.system();
|
||||
|
||||
telemetry.addLine("Ready!");
|
||||
telemetry.update();
|
||||
telemetry.clearAll();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
boolean movingForwards = true;
|
||||
MotionProfile activeProfile = generateProfile(true);
|
||||
double profileStart = clock.seconds();
|
||||
|
||||
|
||||
while (!isStopRequested()) {
|
||||
telemetry.addData("mode", mode);
|
||||
|
||||
switch (mode) {
|
||||
case TUNING_MODE:
|
||||
if (gamepad1.y) {
|
||||
mode = Mode.DRIVER_MODE;
|
||||
drive.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
}
|
||||
|
||||
// calculate and set the motor power
|
||||
double profileTime = clock.seconds() - profileStart;
|
||||
|
||||
if (profileTime > activeProfile.duration()) {
|
||||
// generate a new profile
|
||||
movingForwards = !movingForwards;
|
||||
activeProfile = generateProfile(movingForwards);
|
||||
profileStart = clock.seconds();
|
||||
}
|
||||
|
||||
MotionState motionState = activeProfile.get(profileTime);
|
||||
double targetPower = kV * motionState.getV();
|
||||
drive.setDrivePower(new Pose2d(targetPower, 0, 0));
|
||||
|
||||
List<Double> velocities = drive.getWheelVelocities();
|
||||
|
||||
// update telemetry
|
||||
telemetry.addData("targetVelocity", motionState.getV());
|
||||
for (int i = 0; i < velocities.size(); i++) {
|
||||
telemetry.addData("measuredVelocity" + i, velocities.get(i));
|
||||
telemetry.addData(
|
||||
"error" + i,
|
||||
motionState.getV() - velocities.get(i)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case DRIVER_MODE:
|
||||
if (gamepad1.b) {
|
||||
drive.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
|
||||
|
||||
mode = Mode.TUNING_MODE;
|
||||
movingForwards = true;
|
||||
activeProfile = generateProfile(movingForwards);
|
||||
profileStart = clock.seconds();
|
||||
}
|
||||
|
||||
drive.setWeightedDrivePower(
|
||||
new Pose2d(
|
||||
-gamepad1.left_stick_y,
|
||||
-gamepad1.left_stick_x,
|
||||
-gamepad1.right_stick_x
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (lastKp != MOTOR_VELO_PID.p || lastKd != MOTOR_VELO_PID.d
|
||||
|| lastKi != MOTOR_VELO_PID.i || lastKf != MOTOR_VELO_PID.f) {
|
||||
drive.setPIDFCoefficients(DcMotor.RunMode.RUN_USING_ENCODER, MOTOR_VELO_PID);
|
||||
|
||||
lastKp = MOTOR_VELO_PID.p;
|
||||
lastKi = MOTOR_VELO_PID.i;
|
||||
lastKd = MOTOR_VELO_PID.d;
|
||||
lastKf = MOTOR_VELO_PID.f;
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence;
|
||||
|
||||
/*
|
||||
* Op mode for preliminary tuning of the follower PID coefficients (located in the drive base
|
||||
* classes). The robot drives in a DISTANCE-by-DISTANCE square indefinitely. Utilization of the
|
||||
* dashboard is recommended for this tuning routine. To access the dashboard, connect your computer
|
||||
* to the RC's WiFi network. In your browser, navigate to https://192.168.49.1:8080/dash if you're
|
||||
* using the RC phone or https://192.168.43.1:8080/dash if you are using the Control Hub. Once
|
||||
* you've successfully connected, start the program, and your robot will begin driving in a square.
|
||||
* You should observe the target position (green) and your pose estimate (blue) and adjust your
|
||||
* follower PID coefficients such that you follow the target position as accurately as possible.
|
||||
* If you are using SampleMecanumDrive, you should be tuning TRANSLATIONAL_PID and HEADING_PID.
|
||||
* If you are using SampleTankDrive, you should be tuning AXIAL_PID, CROSS_TRACK_PID, and HEADING_PID.
|
||||
* These coefficients can be tuned live in dashboard.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class FollowerPIDTuner extends LinearOpMode {
|
||||
public static double DISTANCE = 48; // in
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
Pose2d startPose = new Pose2d(-DISTANCE / 2, -DISTANCE / 2, 0);
|
||||
|
||||
drive.setPoseEstimate(startPose);
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
while (!isStopRequested()) {
|
||||
TrajectorySequence trajSeq = drive.trajectorySequenceBuilder(startPose)
|
||||
.forward(DISTANCE)
|
||||
.turn(Math.toRadians(90))
|
||||
.forward(DISTANCE)
|
||||
.turn(Math.toRadians(90))
|
||||
.forward(DISTANCE)
|
||||
.turn(Math.toRadians(90))
|
||||
.forward(DISTANCE)
|
||||
.turn(Math.toRadians(90))
|
||||
.build();
|
||||
drive.followTrajectorySequence(trajSeq);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/**
|
||||
* This is a simple teleop routine for testing localization. Drive the robot around like a normal
|
||||
* teleop routine and make sure the robot's estimated pose matches the robot's actual pose (slight
|
||||
* errors are not out of the ordinary, especially with sudden drive motions). The goal of this
|
||||
* exercise is to ascertain whether the localizer has been configured properly (note: the pure
|
||||
* encoder localizer heading may be significantly off if the track width has not been tuned).
|
||||
*/
|
||||
@TeleOp(group = "drive")
|
||||
public class LocalizationTest extends LinearOpMode {
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
drive.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
|
||||
waitForStart();
|
||||
|
||||
while (!isStopRequested()) {
|
||||
drive.setWeightedDrivePower(
|
||||
new Pose2d(
|
||||
-gamepad1.left_stick_y,
|
||||
-gamepad1.left_stick_x,
|
||||
-gamepad1.right_stick_x
|
||||
)
|
||||
);
|
||||
|
||||
drive.update();
|
||||
|
||||
Pose2d poseEstimate = drive.getPoseEstimate();
|
||||
telemetry.addData("x", poseEstimate.getX());
|
||||
telemetry.addData("y", poseEstimate.getY());
|
||||
telemetry.addData("heading", poseEstimate.getHeading());
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_ACCEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.MAX_VEL;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.RUN_USING_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kA;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kStatic;
|
||||
import static org.firstinspires.ftc.teamcode.drive.DriveConstants.kV;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.drive.DriveSignal;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.kinematics.Kinematics;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfile;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfileGenerator;
|
||||
import com.acmerobotics.roadrunner.profile.MotionState;
|
||||
import com.acmerobotics.roadrunner.util.NanoClock;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.hardware.VoltageSensor;
|
||||
import com.qualcomm.robotcore.util.RobotLog;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/*
|
||||
* This routine is designed to tune the open-loop feedforward coefficients. Although it may seem unnecessary,
|
||||
* tuning these coefficients is just as important as the positional parameters. Like the other
|
||||
* manual tuning routines, this op mode relies heavily upon the dashboard. To access the dashboard,
|
||||
* connect your computer to the RC's WiFi network. In your browser, navigate to
|
||||
* https://192.168.49.1:8080/dash if you're using the RC phone or https://192.168.43.1:8080/dash if
|
||||
* you are using the Control Hub. Once you've successfully connected, start the program, and your
|
||||
* robot will begin moving forward and backward according to a motion profile. Your job is to graph
|
||||
* the velocity errors over time and adjust the feedforward coefficients. Once you've found a
|
||||
* satisfactory set of gains, add them to the appropriate fields in the DriveConstants.java file.
|
||||
*
|
||||
* Pressing Y/Δ (Xbox/PS4) will pause the tuning process and enter driver override, allowing the
|
||||
* user to reset the position of the bot in the event that it drifts off the path.
|
||||
* Pressing B/O (Xbox/PS4) will cede control back to the tuning process.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class ManualFeedforwardTuner extends LinearOpMode {
|
||||
public static double DISTANCE = 72; // in
|
||||
|
||||
private FtcDashboard dashboard = FtcDashboard.getInstance();
|
||||
|
||||
private SampleMecanumDrive drive;
|
||||
|
||||
enum Mode {
|
||||
DRIVER_MODE,
|
||||
TUNING_MODE
|
||||
}
|
||||
|
||||
private Mode mode;
|
||||
|
||||
private static MotionProfile generateProfile(boolean movingForward) {
|
||||
MotionState start = new MotionState(movingForward ? 0 : DISTANCE, 0, 0, 0);
|
||||
MotionState goal = new MotionState(movingForward ? DISTANCE : 0, 0, 0, 0);
|
||||
return MotionProfileGenerator.generateSimpleMotionProfile(start, goal, MAX_VEL, MAX_ACCEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
if (RUN_USING_ENCODER) {
|
||||
RobotLog.setGlobalErrorMsg("Feedforward constants usually don't need to be tuned " +
|
||||
"when using the built-in drive motor velocity PID.");
|
||||
}
|
||||
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, dashboard.getTelemetry());
|
||||
|
||||
drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
final VoltageSensor voltageSensor = hardwareMap.voltageSensor.iterator().next();
|
||||
|
||||
mode = Mode.TUNING_MODE;
|
||||
|
||||
NanoClock clock = NanoClock.system();
|
||||
|
||||
telemetry.addLine("Ready!");
|
||||
telemetry.update();
|
||||
telemetry.clearAll();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
boolean movingForwards = true;
|
||||
MotionProfile activeProfile = generateProfile(true);
|
||||
double profileStart = clock.seconds();
|
||||
|
||||
|
||||
while (!isStopRequested()) {
|
||||
telemetry.addData("mode", mode);
|
||||
|
||||
switch (mode) {
|
||||
case TUNING_MODE:
|
||||
if (gamepad1.y) {
|
||||
mode = Mode.DRIVER_MODE;
|
||||
}
|
||||
|
||||
// calculate and set the motor power
|
||||
double profileTime = clock.seconds() - profileStart;
|
||||
|
||||
if (profileTime > activeProfile.duration()) {
|
||||
// generate a new profile
|
||||
movingForwards = !movingForwards;
|
||||
activeProfile = generateProfile(movingForwards);
|
||||
profileStart = clock.seconds();
|
||||
}
|
||||
|
||||
MotionState motionState = activeProfile.get(profileTime);
|
||||
double targetPower = Kinematics.calculateMotorFeedforward(motionState.getV(), motionState.getA(), kV, kA, kStatic);
|
||||
|
||||
final double NOMINAL_VOLTAGE = 12.0;
|
||||
final double voltage = voltageSensor.getVoltage();
|
||||
drive.setDrivePower(new Pose2d(NOMINAL_VOLTAGE / voltage * targetPower, 0, 0));
|
||||
drive.updatePoseEstimate();
|
||||
|
||||
Pose2d poseVelo = Objects.requireNonNull(drive.getPoseVelocity(), "poseVelocity() must not be null. Ensure that the getWheelVelocities() method has been overridden in your localizer.");
|
||||
double currentVelo = poseVelo.getX();
|
||||
|
||||
// update telemetry
|
||||
telemetry.addData("targetVelocity", motionState.getV());
|
||||
telemetry.addData("measuredVelocity", currentVelo);
|
||||
telemetry.addData("error", motionState.getV() - currentVelo);
|
||||
break;
|
||||
case DRIVER_MODE:
|
||||
if (gamepad1.b) {
|
||||
mode = Mode.TUNING_MODE;
|
||||
movingForwards = true;
|
||||
activeProfile = generateProfile(movingForwards);
|
||||
profileStart = clock.seconds();
|
||||
}
|
||||
|
||||
drive.setWeightedDrivePower(
|
||||
new Pose2d(
|
||||
-gamepad1.left_stick_y,
|
||||
-gamepad1.left_stick_x,
|
||||
-gamepad1.right_stick_x
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.util.ElapsedTime;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This routine is designed to calculate the maximum angular velocity your bot can achieve under load.
|
||||
* <p>
|
||||
* Upon pressing start, your bot will turn at max power for RUNTIME seconds.
|
||||
* <p>
|
||||
* Further fine tuning of MAX_ANG_VEL may be desired.
|
||||
*/
|
||||
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class MaxAngularVeloTuner extends LinearOpMode {
|
||||
public static double RUNTIME = 4.0;
|
||||
|
||||
private ElapsedTime timer;
|
||||
private double maxAngVelocity = 0.0;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
drive.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
telemetry.addLine("Your bot will turn at full speed for " + RUNTIME + " seconds.");
|
||||
telemetry.addLine("Please ensure you have enough space cleared.");
|
||||
telemetry.addLine("");
|
||||
telemetry.addLine("Press start when ready.");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.update();
|
||||
|
||||
drive.setDrivePower(new Pose2d(0, 0, 1));
|
||||
timer = new ElapsedTime();
|
||||
|
||||
while (!isStopRequested() && timer.seconds() < RUNTIME) {
|
||||
drive.updatePoseEstimate();
|
||||
|
||||
Pose2d poseVelo = Objects.requireNonNull(drive.getPoseVelocity(), "poseVelocity() must not be null. Ensure that the getWheelVelocities() method has been overridden in your localizer.");
|
||||
|
||||
maxAngVelocity = Math.max(poseVelo.getHeading(), maxAngVelocity);
|
||||
}
|
||||
|
||||
drive.setDrivePower(new Pose2d());
|
||||
|
||||
telemetry.addData("Max Angular Velocity (rad)", maxAngVelocity);
|
||||
telemetry.addData("Max Angular Velocity (deg)", Math.toDegrees(maxAngVelocity));
|
||||
telemetry.addData("Max Recommended Angular Velocity (rad)", maxAngVelocity * 0.8);
|
||||
telemetry.addData("Max Recommended Angular Velocity (deg)", Math.toDegrees(maxAngVelocity * 0.8));
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested()) idle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.VoltageSensor;
|
||||
import com.qualcomm.robotcore.util.ElapsedTime;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This routine is designed to calculate the maximum velocity your bot can achieve under load. It
|
||||
* will also calculate the effective kF value for your velocity PID.
|
||||
* <p>
|
||||
* Upon pressing start, your bot will run at max power for RUNTIME seconds.
|
||||
* <p>
|
||||
* Further fine tuning of kF may be desired.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class MaxVelocityTuner extends LinearOpMode {
|
||||
public static double RUNTIME = 2.0;
|
||||
|
||||
private ElapsedTime timer;
|
||||
private double maxVelocity = 0.0;
|
||||
|
||||
private VoltageSensor batteryVoltageSensor;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
drive.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
|
||||
batteryVoltageSensor = hardwareMap.voltageSensor.iterator().next();
|
||||
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
telemetry.addLine("Your bot will go at full speed for " + RUNTIME + " seconds.");
|
||||
telemetry.addLine("Please ensure you have enough space cleared.");
|
||||
telemetry.addLine("");
|
||||
telemetry.addLine("Press start when ready.");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.update();
|
||||
|
||||
drive.setDrivePower(new Pose2d(1, 0, 0));
|
||||
timer = new ElapsedTime();
|
||||
|
||||
while (!isStopRequested() && timer.seconds() < RUNTIME) {
|
||||
drive.updatePoseEstimate();
|
||||
|
||||
Pose2d poseVelo = Objects.requireNonNull(drive.getPoseVelocity(), "poseVelocity() must not be null. Ensure that the getWheelVelocities() method has been overridden in your localizer.");
|
||||
|
||||
maxVelocity = Math.max(poseVelo.vec().norm(), maxVelocity);
|
||||
}
|
||||
|
||||
drive.setDrivePower(new Pose2d());
|
||||
|
||||
double effectiveKf = DriveConstants.getMotorVelocityF(veloInchesToTicks(maxVelocity));
|
||||
|
||||
telemetry.addData("Max Velocity", maxVelocity);
|
||||
telemetry.addData("Max Recommended Velocity", maxVelocity * 0.8);
|
||||
telemetry.addData("Voltage Compensated kF", effectiveKf * batteryVoltageSensor.getVoltage() / 12);
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested() && opModeIsActive()) idle();
|
||||
}
|
||||
|
||||
private double veloInchesToTicks(double inchesPerSec) {
|
||||
return inchesPerSec / (2 * Math.PI * DriveConstants.WHEEL_RADIUS) / DriveConstants.GEAR_RATIO * DriveConstants.TICKS_PER_REV;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/**
|
||||
* This is a simple teleop routine for debugging your motor configuration.
|
||||
* Pressing each of the buttons will power its respective motor.
|
||||
*
|
||||
* Button Mappings:
|
||||
*
|
||||
* Xbox/PS4 Button - Motor
|
||||
* X / ▢ - Front Left
|
||||
* Y / Δ - Front Right
|
||||
* B / O - Rear Right
|
||||
* A / X - Rear Left
|
||||
* The buttons are mapped to match the wheels spatially if you
|
||||
* were to rotate the gamepad 45deg°. x/square is the front left
|
||||
* ________ and each button corresponds to the wheel as you go clockwise
|
||||
* / ______ \
|
||||
* ------------.-' _ '-..+ Front of Bot
|
||||
* / _ ( Y ) _ \ ^
|
||||
* | ( X ) _ ( B ) | Front Left \ Front Right
|
||||
* ___ '. ( A ) /| Wheel \ Wheel
|
||||
* .' '. '-._____.-' .' (x/▢) \ (Y/Δ)
|
||||
* | | | \
|
||||
* '.___.' '. | Rear Left \ Rear Right
|
||||
* '. / Wheel \ Wheel
|
||||
* \. .' (A/X) \ (B/O)
|
||||
* \________/
|
||||
*
|
||||
* Uncomment the @Disabled tag below to use this opmode.
|
||||
*/
|
||||
@Disabled
|
||||
@Config
|
||||
@TeleOp(group = "drive")
|
||||
public class MotorDirectionDebugger extends LinearOpMode {
|
||||
public static double MOTOR_POWER = 0.7;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
telemetry.addLine("Press play to begin the debugging opmode");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.setDisplayFormat(Telemetry.DisplayFormat.HTML);
|
||||
|
||||
while (!isStopRequested()) {
|
||||
telemetry.addLine("Press each button to turn on its respective motor");
|
||||
telemetry.addLine();
|
||||
telemetry.addLine("<font face=\"monospace\">Xbox/PS4 Button - Motor</font>");
|
||||
telemetry.addLine("<font face=\"monospace\"> X / ▢ - Front Left</font>");
|
||||
telemetry.addLine("<font face=\"monospace\"> Y / Δ - Front Right</font>");
|
||||
telemetry.addLine("<font face=\"monospace\"> B / O - Rear Right</font>");
|
||||
telemetry.addLine("<font face=\"monospace\"> A / X - Rear Left</font>");
|
||||
telemetry.addLine();
|
||||
|
||||
if(gamepad1.x) {
|
||||
drive.setMotorPowers(MOTOR_POWER, 0, 0, 0);
|
||||
telemetry.addLine("Running Motor: Front Left");
|
||||
} else if(gamepad1.y) {
|
||||
drive.setMotorPowers(0, 0, 0, MOTOR_POWER);
|
||||
telemetry.addLine("Running Motor: Front Right");
|
||||
} else if(gamepad1.b) {
|
||||
drive.setMotorPowers(0, 0, MOTOR_POWER, 0);
|
||||
telemetry.addLine("Running Motor: Rear Right");
|
||||
} else if(gamepad1.a) {
|
||||
drive.setMotorPowers(0, MOTOR_POWER, 0, 0);
|
||||
telemetry.addLine("Running Motor: Rear Left");
|
||||
} else {
|
||||
drive.setMotorPowers(0, 0, 0, 0);
|
||||
telemetry.addLine("Running Motor: None");
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.geometry.Vector2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/*
|
||||
* This is an example of a more complex path to really test the tuning.
|
||||
*/
|
||||
@Autonomous(group = "drive")
|
||||
public class SplineTest extends LinearOpMode {
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
Trajectory traj = drive.trajectoryBuilder(new Pose2d())
|
||||
.splineTo(new Vector2d(30, 30), 0)
|
||||
.build();
|
||||
|
||||
drive.followTrajectory(traj);
|
||||
|
||||
sleep(2000);
|
||||
|
||||
drive.followTrajectory(
|
||||
drive.trajectoryBuilder(traj.end(), true)
|
||||
.splineTo(new Vector2d(0, 0), Math.toRadians(180))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/*
|
||||
* This is a simple routine to test translational drive capabilities.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class StrafeTest extends LinearOpMode {
|
||||
public static double DISTANCE = 60; // in
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
Trajectory trajectory = drive.trajectoryBuilder(new Pose2d())
|
||||
.strafeRight(DISTANCE)
|
||||
.build();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
drive.followTrajectory(trajectory);
|
||||
|
||||
Pose2d poseEstimate = drive.getPoseEstimate();
|
||||
telemetry.addData("finalX", poseEstimate.getX());
|
||||
telemetry.addData("finalY", poseEstimate.getY());
|
||||
telemetry.addData("finalHeading", poseEstimate.getHeading());
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested() && opModeIsActive()) ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/*
|
||||
* This is a simple routine to test translational drive capabilities.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class StraightTest extends LinearOpMode {
|
||||
public static double DISTANCE = 60; // in
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
Trajectory trajectory = drive.trajectoryBuilder(new Pose2d())
|
||||
.forward(DISTANCE)
|
||||
.build();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
drive.followTrajectory(trajectory);
|
||||
|
||||
Pose2d poseEstimate = drive.getPoseEstimate();
|
||||
telemetry.addData("finalX", poseEstimate.getX());
|
||||
telemetry.addData("finalY", poseEstimate.getY());
|
||||
telemetry.addData("finalHeading", poseEstimate.getHeading());
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested() && opModeIsActive()) ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.util.Angle;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.util.MovingStatistics;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.robotcore.internal.system.Misc;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/*
|
||||
* This routine determines the effective track width. The procedure works by executing a point turn
|
||||
* with a given angle and measuring the difference between that angle and the actual angle (as
|
||||
* indicated by an external IMU/gyro, track wheels, or some other localizer). The quotient
|
||||
* given angle / actual angle gives a multiplicative adjustment to the estimated track width
|
||||
* (effective track width = estimated track width * given angle / actual angle). The routine repeats
|
||||
* this procedure a few times and averages the values for additional accuracy. Note: a relatively
|
||||
* accurate track width estimate is important or else the angular constraints will be thrown off.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class TrackWidthTuner extends LinearOpMode {
|
||||
public static double ANGLE = 180; // deg
|
||||
public static int NUM_TRIALS = 5;
|
||||
public static int DELAY = 1000; // ms
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
// TODO: if you haven't already, set the localizer to something that doesn't depend on
|
||||
// drive encoders for computing the heading
|
||||
|
||||
telemetry.addLine("Press play to begin the track width tuner routine");
|
||||
telemetry.addLine("Make sure your robot has enough clearance to turn smoothly");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Running...");
|
||||
telemetry.update();
|
||||
|
||||
MovingStatistics trackWidthStats = new MovingStatistics(NUM_TRIALS);
|
||||
for (int i = 0; i < NUM_TRIALS; i++) {
|
||||
drive.setPoseEstimate(new Pose2d());
|
||||
|
||||
// it is important to handle heading wraparounds
|
||||
double headingAccumulator = 0;
|
||||
double lastHeading = 0;
|
||||
|
||||
drive.turnAsync(Math.toRadians(ANGLE));
|
||||
|
||||
while (!isStopRequested() && drive.isBusy()) {
|
||||
double heading = drive.getPoseEstimate().getHeading();
|
||||
headingAccumulator += Angle.normDelta(heading - lastHeading);
|
||||
lastHeading = heading;
|
||||
|
||||
drive.update();
|
||||
}
|
||||
|
||||
double trackWidth = DriveConstants.TRACK_WIDTH * Math.toRadians(ANGLE) / headingAccumulator;
|
||||
trackWidthStats.add(trackWidth);
|
||||
|
||||
sleep(DELAY);
|
||||
}
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Tuning complete");
|
||||
telemetry.addLine(Misc.formatInvariant("Effective track width = %.2f (SE = %.3f)",
|
||||
trackWidthStats.getMean(),
|
||||
trackWidthStats.getStandardDeviation() / Math.sqrt(NUM_TRIALS)));
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested()) {
|
||||
idle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.util.Angle;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.util.MovingStatistics;
|
||||
import com.qualcomm.robotcore.util.RobotLog;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.robotcore.internal.system.Misc;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.StandardTrackingWheelLocalizer;
|
||||
|
||||
/**
|
||||
* This routine determines the effective forward offset for the lateral tracking wheel.
|
||||
* The procedure executes a point turn at a given angle for a certain number of trials,
|
||||
* along with a specified delay in milliseconds. The purpose of this is to track the
|
||||
* change in the y position during the turn. The offset, or distance, of the lateral tracking
|
||||
* wheel from the center or rotation allows the wheel to spin during a point turn, leading
|
||||
* to an incorrect measurement for the y position. This creates an arc around around
|
||||
* the center of rotation with an arc length of change in y and a radius equal to the forward
|
||||
* offset. We can compute this offset by calculating (change in y position) / (change in heading)
|
||||
* which returns the radius if the angle (change in heading) is in radians. This is based
|
||||
* on the arc length formula of length = theta * radius.
|
||||
*
|
||||
* To run this routine, simply adjust the desired angle and specify the number of trials
|
||||
* and the desired delay. Then, run the procedure. Once it finishes, it will print the
|
||||
* average of all the calculated forward offsets derived from the calculation. This calculated
|
||||
* forward offset is then added onto the current forward offset to produce an overall estimate
|
||||
* for the forward offset. You can run this procedure as many times as necessary until a
|
||||
* satisfactory result is produced.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group="drive")
|
||||
public class TrackingWheelForwardOffsetTuner extends LinearOpMode {
|
||||
public static double ANGLE = 180; // deg
|
||||
public static int NUM_TRIALS = 5;
|
||||
public static int DELAY = 1000; // ms
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
Telemetry telemetry = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
if (!(drive.getLocalizer() instanceof StandardTrackingWheelLocalizer)) {
|
||||
RobotLog.setGlobalErrorMsg("StandardTrackingWheelLocalizer is not being set in the "
|
||||
+ "drive class. Ensure that \"setLocalizer(new StandardTrackingWheelLocalizer"
|
||||
+ "(hardwareMap));\" is called in SampleMecanumDrive.java");
|
||||
}
|
||||
|
||||
telemetry.addLine("Press play to begin the forward offset tuner");
|
||||
telemetry.addLine("Make sure your robot has enough clearance to turn smoothly");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Running...");
|
||||
telemetry.update();
|
||||
|
||||
MovingStatistics forwardOffsetStats = new MovingStatistics(NUM_TRIALS);
|
||||
for (int i = 0; i < NUM_TRIALS; i++) {
|
||||
drive.setPoseEstimate(new Pose2d());
|
||||
|
||||
// it is important to handle heading wraparounds
|
||||
double headingAccumulator = 0;
|
||||
double lastHeading = 0;
|
||||
|
||||
drive.turnAsync(Math.toRadians(ANGLE));
|
||||
|
||||
while (!isStopRequested() && drive.isBusy()) {
|
||||
double heading = drive.getPoseEstimate().getHeading();
|
||||
headingAccumulator += Angle.norm(heading - lastHeading);
|
||||
lastHeading = heading;
|
||||
|
||||
drive.update();
|
||||
}
|
||||
|
||||
double forwardOffset = StandardTrackingWheelLocalizer.FORWARD_OFFSET +
|
||||
drive.getPoseEstimate().getY() / headingAccumulator;
|
||||
forwardOffsetStats.add(forwardOffset);
|
||||
|
||||
sleep(DELAY);
|
||||
}
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Tuning complete");
|
||||
telemetry.addLine(Misc.formatInvariant("Effective forward offset = %.2f (SE = %.3f)",
|
||||
forwardOffsetStats.getMean(),
|
||||
forwardOffsetStats.getStandardDeviation() / Math.sqrt(NUM_TRIALS)));
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested()) {
|
||||
idle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.util.Angle;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.util.RobotLog;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.StandardTrackingWheelLocalizer;
|
||||
|
||||
/**
|
||||
* Opmode designed to assist the user in tuning the `StandardTrackingWheelLocalizer`'s
|
||||
* LATERAL_DISTANCE value. The LATERAL_DISTANCE is the center-to-center distance of the parallel
|
||||
* wheels.
|
||||
*
|
||||
* Tuning Routine:
|
||||
*
|
||||
* 1. Set the LATERAL_DISTANCE value in StandardTrackingWheelLocalizer.java to the physical
|
||||
* measured value. This need only be an estimated value as you will be tuning it anyways.
|
||||
*
|
||||
* 2. Make a mark on the bot (with a piece of tape or sharpie or however you wish) and make an
|
||||
* similar mark right below the indicator on your bot. This will be your reference point to
|
||||
* ensure you've turned exactly 360°.
|
||||
*
|
||||
* 3. Although not entirely necessary, having the bot's pose being drawn in dashbooard does help
|
||||
* identify discrepancies in the LATERAL_DISTANCE value. To access the dashboard,
|
||||
* connect your computer to the RC's WiFi network. In your browser, navigate to
|
||||
* https://192.168.49.1:8080/dash if you're using the RC phone or https://192.168.43.1:8080/dash
|
||||
* if you are using the Control Hub.
|
||||
* Ensure the field is showing (select the field view in top right of the page).
|
||||
*
|
||||
* 4. Press play to begin the tuning routine.
|
||||
*
|
||||
* 5. Use the right joystick on gamepad 1 to turn the bot counterclockwise.
|
||||
*
|
||||
* 6. Spin the bot 10 times, counterclockwise. Make sure to keep track of these turns.
|
||||
*
|
||||
* 7. Once the bot has finished spinning 10 times, press A to finishing the routine. The indicators
|
||||
* on the bot and on the ground you created earlier should be lined up.
|
||||
*
|
||||
* 8. Your effective LATERAL_DISTANCE will be given. Stick this value into your
|
||||
* StandardTrackingWheelLocalizer.java class.
|
||||
*
|
||||
* 9. If this value is incorrect, run the routine again while adjusting the LATERAL_DISTANCE value
|
||||
* yourself. Read the heading output and follow the advice stated in the note below to manually
|
||||
* nudge the values yourself.
|
||||
*
|
||||
* Note:
|
||||
* It helps to pay attention to how the pose on the field is drawn in dashboard. A blue circle with
|
||||
* a line from the circumference to the center should be present, representing the bot. The line
|
||||
* indicates forward. If your LATERAL_DISTANCE value is tuned currently, the pose drawn in
|
||||
* dashboard should keep track with the pose of your actual bot. If the drawn bot turns slower than
|
||||
* the actual bot, the LATERAL_DISTANCE should be decreased. If the drawn bot turns faster than the
|
||||
* actual bot, the LATERAL_DISTANCE should be increased.
|
||||
*
|
||||
* If your drawn bot oscillates around a point in dashboard, don't worry. This is because the
|
||||
* position of the perpendicular wheel isn't perfectly set and causes a discrepancy in the
|
||||
* effective center of rotation. You can ignore this effect. The center of rotation will be offset
|
||||
* slightly but your heading will still be fine. This does not affect your overall tracking
|
||||
* precision. The heading should still line up.
|
||||
*/
|
||||
@Config
|
||||
@TeleOp(group = "drive")
|
||||
public class TrackingWheelLateralDistanceTuner extends LinearOpMode {
|
||||
public static int NUM_TURNS = 10;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
if (!(drive.getLocalizer() instanceof StandardTrackingWheelLocalizer)) {
|
||||
RobotLog.setGlobalErrorMsg("StandardTrackingWheelLocalizer is not being set in the "
|
||||
+ "drive class. Ensure that \"setLocalizer(new StandardTrackingWheelLocalizer"
|
||||
+ "(hardwareMap));\" is called in SampleMecanumDrive.java");
|
||||
}
|
||||
|
||||
telemetry.addLine("Prior to beginning the routine, please read the directions "
|
||||
+ "located in the comments of the opmode file.");
|
||||
telemetry.addLine("Press play to begin the tuning routine.");
|
||||
telemetry.addLine("");
|
||||
telemetry.addLine("Press Y/△ to stop the routine.");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.update();
|
||||
|
||||
double headingAccumulator = 0;
|
||||
double lastHeading = 0;
|
||||
|
||||
boolean tuningFinished = false;
|
||||
|
||||
while (!isStopRequested() && !tuningFinished) {
|
||||
Pose2d vel = new Pose2d(0, 0, -gamepad1.right_stick_x);
|
||||
drive.setDrivePower(vel);
|
||||
|
||||
drive.update();
|
||||
|
||||
double heading = drive.getPoseEstimate().getHeading();
|
||||
double deltaHeading = heading - lastHeading;
|
||||
|
||||
headingAccumulator += Angle.normDelta(deltaHeading);
|
||||
lastHeading = heading;
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Total Heading (deg): " + Math.toDegrees(headingAccumulator));
|
||||
telemetry.addLine("Raw Heading (deg): " + Math.toDegrees(heading));
|
||||
telemetry.addLine();
|
||||
telemetry.addLine("Press Y/△ to conclude routine");
|
||||
telemetry.update();
|
||||
|
||||
if (gamepad1.y)
|
||||
tuningFinished = true;
|
||||
}
|
||||
|
||||
telemetry.clearAll();
|
||||
telemetry.addLine("Localizer's total heading: " + Math.toDegrees(headingAccumulator) + "°");
|
||||
telemetry.addLine("Effective LATERAL_DISTANCE: " +
|
||||
(headingAccumulator / (NUM_TURNS * Math.PI * 2)) * StandardTrackingWheelLocalizer.LATERAL_DISTANCE);
|
||||
|
||||
telemetry.update();
|
||||
|
||||
while (!isStopRequested()) idle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.firstinspires.ftc.teamcode.drive.opmode;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
|
||||
/*
|
||||
* This is a simple routine to test turning capabilities.
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(group = "drive")
|
||||
public class TurnTest extends LinearOpMode {
|
||||
public static double ANGLE = 90; // deg
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
SampleMecanumDrive drive = new SampleMecanumDrive(hardwareMap);
|
||||
|
||||
waitForStart();
|
||||
|
||||
if (isStopRequested()) return;
|
||||
|
||||
drive.turn(Math.toRadians(ANGLE));
|
||||
}
|
||||
}
|
||||
131
TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md
Normal file
131
TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md
Normal file
@@ -0,0 +1,131 @@
|
||||
## TeamCode Module
|
||||
|
||||
Welcome!
|
||||
|
||||
This module, TeamCode, is the place where you will write/paste the code for your team's
|
||||
robot controller App. This module is currently empty (a clean slate) but the
|
||||
process for adding OpModes is straightforward.
|
||||
|
||||
## Creating your own OpModes
|
||||
|
||||
The easiest way to create your own OpMode is to copy a Sample OpMode and make it your own.
|
||||
|
||||
Sample opmodes exist in the FtcRobotController module.
|
||||
To locate these samples, find the FtcRobotController module in the "Project/Android" tab.
|
||||
|
||||
Expand the following tree elements:
|
||||
FtcRobotController/java/org.firstinspires.ftc.robotcontroller/external/samples
|
||||
|
||||
### Naming of Samples
|
||||
|
||||
To gain a better understanding of how the samples are organized, and how to interpret the
|
||||
naming system, it will help to understand the conventions that were used during their creation.
|
||||
|
||||
These conventions are described (in detail) in the sample_conventions.md file in this folder.
|
||||
|
||||
To summarize: A range of different samples classes will reside in the java/external/samples.
|
||||
The class names will follow a naming convention which indicates the purpose of each class.
|
||||
The prefix of the name will be one of the following:
|
||||
|
||||
Basic: This is a minimally functional OpMode used to illustrate the skeleton/structure
|
||||
of a particular style of OpMode. These are bare bones examples.
|
||||
|
||||
Sensor: This is a Sample OpMode that shows how to use a specific sensor.
|
||||
It is not intended to drive a functioning robot, it is simply showing the minimal code
|
||||
required to read and display the sensor values.
|
||||
|
||||
Robot: This is a Sample OpMode that assumes a simple two-motor (differential) drive base.
|
||||
It may be used to provide a common baseline driving OpMode, or
|
||||
to demonstrate how a particular sensor or concept can be used to navigate.
|
||||
|
||||
Concept: This is a sample OpMode that illustrates performing a specific function or concept.
|
||||
These may be complex, but their operation should be explained clearly in the comments,
|
||||
or the comments should reference an external doc, guide or tutorial.
|
||||
Each OpMode should try to only demonstrate a single concept so they are easy to
|
||||
locate based on their name. These OpModes may not produce a drivable robot.
|
||||
|
||||
After the prefix, other conventions will apply:
|
||||
|
||||
* Sensor class names are constructed as: Sensor - Company - Type
|
||||
* Robot class names are constructed as: Robot - Mode - Action - OpModetype
|
||||
* Concept class names are constructed as: Concept - Topic - OpModetype
|
||||
|
||||
Once you are familiar with the range of samples available, you can choose one to be the
|
||||
basis for your own robot. In all cases, the desired sample(s) needs to be copied into
|
||||
your TeamCode module to be used.
|
||||
|
||||
This is done inside Android Studio directly, using the following steps:
|
||||
|
||||
1) Locate the desired sample class in the Project/Android tree.
|
||||
|
||||
2) Right click on the sample class and select "Copy"
|
||||
|
||||
3) Expand the TeamCode/java folder
|
||||
|
||||
4) Right click on the org.firstinspires.ftc.teamcode folder and select "Paste"
|
||||
|
||||
5) You will be prompted for a class name for the copy.
|
||||
Choose something meaningful based on the purpose of this class.
|
||||
Start with a capital letter, and remember that there may be more similar classes later.
|
||||
|
||||
Once your copy has been created, you should prepare it for use on your robot.
|
||||
This is done by adjusting the OpMode's name, and enabling it to be displayed on the
|
||||
Driver Station's OpMode list.
|
||||
|
||||
Each OpMode sample class begins with several lines of code like the ones shown below:
|
||||
|
||||
```
|
||||
@TeleOp(name="Template: Linear OpMode", group="Linear Opmode")
|
||||
@Disabled
|
||||
```
|
||||
|
||||
The name that will appear on the driver station's "opmode list" is defined by the code:
|
||||
``name="Template: Linear OpMode"``
|
||||
You can change what appears between the quotes to better describe your opmode.
|
||||
The "group=" portion of the code can be used to help organize your list of OpModes.
|
||||
|
||||
As shown, the current OpMode will NOT appear on the driver station's OpMode list because of the
|
||||
``@Disabled`` annotation which has been included.
|
||||
This line can simply be deleted , or commented out, to make the OpMode visible.
|
||||
|
||||
|
||||
|
||||
## ADVANCED Multi-Team App management: Cloning the TeamCode Module
|
||||
|
||||
In some situations, you have multiple teams in your club and you want them to all share
|
||||
a common code organization, with each being able to *see* the others code but each having
|
||||
their own team module with their own code that they maintain themselves.
|
||||
|
||||
In this situation, you might wish to clone the TeamCode module, once for each of these teams.
|
||||
Each of the clones would then appear along side each other in the Android Studio module list,
|
||||
together with the FtcRobotController module (and the original TeamCode module).
|
||||
|
||||
Selective Team phones can then be programmed by selecting the desired Module from the pulldown list
|
||||
prior to clicking to the green Run arrow.
|
||||
|
||||
Warning: This is not for the inexperienced Software developer.
|
||||
You will need to be comfortable with File manipulations and managing Android Studio Modules.
|
||||
These changes are performed OUTSIDE of Android Studios, so close Android Studios before you do this.
|
||||
|
||||
Also.. Make a full project backup before you start this :)
|
||||
|
||||
To clone TeamCode, do the following:
|
||||
|
||||
Note: Some names start with "Team" and others start with "team". This is intentional.
|
||||
|
||||
1) Using your operating system file management tools, copy the whole "TeamCode"
|
||||
folder to a sibling folder with a corresponding new name, eg: "Team0417".
|
||||
|
||||
2) In the new Team0417 folder, delete the TeamCode.iml file.
|
||||
|
||||
3) the new Team0417 folder, rename the "src/main/java/org/firstinspires/ftc/teamcode" folder
|
||||
to a matching name with a lowercase 'team' eg: "team0417".
|
||||
|
||||
4) In the new Team0417/src/main folder, edit the "AndroidManifest.xml" file, change the line that contains
|
||||
package="org.firstinspires.ftc.teamcode"
|
||||
to be
|
||||
package="org.firstinspires.ftc.team0417"
|
||||
|
||||
5) Add: include ':Team0417' to the "/settings.gradle" file.
|
||||
|
||||
6) Open up Android Studios and clean out any old files by using the menu to "Build/Clean Project""
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence;
|
||||
|
||||
|
||||
public class EmptySequenceException extends RuntimeException { }
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.SequenceSegment;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TrajectorySequence {
|
||||
private final List<SequenceSegment> sequenceList;
|
||||
|
||||
public TrajectorySequence(List<SequenceSegment> sequenceList) {
|
||||
if (sequenceList.size() == 0) throw new EmptySequenceException();
|
||||
|
||||
this.sequenceList = Collections.unmodifiableList(sequenceList);
|
||||
}
|
||||
|
||||
public Pose2d start() {
|
||||
return sequenceList.get(0).getStartPose();
|
||||
}
|
||||
|
||||
public Pose2d end() {
|
||||
return sequenceList.get(sequenceList.size() - 1).getEndPose();
|
||||
}
|
||||
|
||||
public double duration() {
|
||||
double total = 0.0;
|
||||
|
||||
for (SequenceSegment segment : sequenceList) {
|
||||
total += segment.getDuration();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public SequenceSegment get(int i) {
|
||||
return sequenceList.get(i);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return sequenceList.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.geometry.Vector2d;
|
||||
import com.acmerobotics.roadrunner.path.PathContinuityViolationException;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfile;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfileGenerator;
|
||||
import com.acmerobotics.roadrunner.profile.MotionState;
|
||||
import com.acmerobotics.roadrunner.trajectory.DisplacementMarker;
|
||||
import com.acmerobotics.roadrunner.trajectory.DisplacementProducer;
|
||||
import com.acmerobotics.roadrunner.trajectory.MarkerCallback;
|
||||
import com.acmerobotics.roadrunner.trajectory.SpatialMarker;
|
||||
import com.acmerobotics.roadrunner.trajectory.TemporalMarker;
|
||||
import com.acmerobotics.roadrunner.trajectory.TimeProducer;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryMarker;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryAccelerationConstraint;
|
||||
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryVelocityConstraint;
|
||||
import com.acmerobotics.roadrunner.util.Angle;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.SequenceSegment;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.TrajectorySegment;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.TurnSegment;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.WaitSegment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class TrajectorySequenceBuilder {
|
||||
private final double resolution = 0.25;
|
||||
|
||||
private final TrajectoryVelocityConstraint baseVelConstraint;
|
||||
private final TrajectoryAccelerationConstraint baseAccelConstraint;
|
||||
|
||||
private TrajectoryVelocityConstraint currentVelConstraint;
|
||||
private TrajectoryAccelerationConstraint currentAccelConstraint;
|
||||
|
||||
private final double baseTurnConstraintMaxAngVel;
|
||||
private final double baseTurnConstraintMaxAngAccel;
|
||||
|
||||
private double currentTurnConstraintMaxAngVel;
|
||||
private double currentTurnConstraintMaxAngAccel;
|
||||
|
||||
private final List<SequenceSegment> sequenceSegments;
|
||||
|
||||
private final List<TemporalMarker> temporalMarkers;
|
||||
private final List<DisplacementMarker> displacementMarkers;
|
||||
private final List<SpatialMarker> spatialMarkers;
|
||||
|
||||
private Pose2d lastPose;
|
||||
|
||||
private double tangentOffset;
|
||||
|
||||
private boolean setAbsoluteTangent;
|
||||
private double absoluteTangent;
|
||||
|
||||
private TrajectoryBuilder currentTrajectoryBuilder;
|
||||
|
||||
private double currentDuration;
|
||||
private double currentDisplacement;
|
||||
|
||||
private double lastDurationTraj;
|
||||
private double lastDisplacementTraj;
|
||||
|
||||
public TrajectorySequenceBuilder(
|
||||
Pose2d startPose,
|
||||
Double startTangent,
|
||||
TrajectoryVelocityConstraint baseVelConstraint,
|
||||
TrajectoryAccelerationConstraint baseAccelConstraint,
|
||||
double baseTurnConstraintMaxAngVel,
|
||||
double baseTurnConstraintMaxAngAccel
|
||||
) {
|
||||
this.baseVelConstraint = baseVelConstraint;
|
||||
this.baseAccelConstraint = baseAccelConstraint;
|
||||
|
||||
this.currentVelConstraint = baseVelConstraint;
|
||||
this.currentAccelConstraint = baseAccelConstraint;
|
||||
|
||||
this.baseTurnConstraintMaxAngVel = baseTurnConstraintMaxAngVel;
|
||||
this.baseTurnConstraintMaxAngAccel = baseTurnConstraintMaxAngAccel;
|
||||
|
||||
this.currentTurnConstraintMaxAngVel = baseTurnConstraintMaxAngVel;
|
||||
this.currentTurnConstraintMaxAngAccel = baseTurnConstraintMaxAngAccel;
|
||||
|
||||
sequenceSegments = new ArrayList<>();
|
||||
|
||||
temporalMarkers = new ArrayList<>();
|
||||
displacementMarkers = new ArrayList<>();
|
||||
spatialMarkers = new ArrayList<>();
|
||||
|
||||
lastPose = startPose;
|
||||
|
||||
tangentOffset = 0.0;
|
||||
|
||||
setAbsoluteTangent = (startTangent != null);
|
||||
absoluteTangent = startTangent != null ? startTangent : 0.0;
|
||||
|
||||
currentTrajectoryBuilder = null;
|
||||
|
||||
currentDuration = 0.0;
|
||||
currentDisplacement = 0.0;
|
||||
|
||||
lastDurationTraj = 0.0;
|
||||
lastDisplacementTraj = 0.0;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder(
|
||||
Pose2d startPose,
|
||||
TrajectoryVelocityConstraint baseVelConstraint,
|
||||
TrajectoryAccelerationConstraint baseAccelConstraint,
|
||||
double baseTurnConstraintMaxAngVel,
|
||||
double baseTurnConstraintMaxAngAccel
|
||||
) {
|
||||
this(
|
||||
startPose, null,
|
||||
baseVelConstraint, baseAccelConstraint,
|
||||
baseTurnConstraintMaxAngVel, baseTurnConstraintMaxAngAccel
|
||||
);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineTo(Vector2d endPosition) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineTo(endPosition, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineTo(
|
||||
Vector2d endPosition,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineTo(endPosition, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineToConstantHeading(Vector2d endPosition) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineToConstantHeading(endPosition, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineToConstantHeading(
|
||||
Vector2d endPosition,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineToConstantHeading(endPosition, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineToLinearHeading(Pose2d endPose) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineToLinearHeading(endPose, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineToLinearHeading(
|
||||
Pose2d endPose,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineToLinearHeading(endPose, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineToSplineHeading(Pose2d endPose) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineToSplineHeading(endPose, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder lineToSplineHeading(
|
||||
Pose2d endPose,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.lineToSplineHeading(endPose, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder strafeTo(Vector2d endPosition) {
|
||||
return addPath(() -> currentTrajectoryBuilder.strafeTo(endPosition, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder strafeTo(
|
||||
Vector2d endPosition,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.strafeTo(endPosition, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder forward(double distance) {
|
||||
return addPath(() -> currentTrajectoryBuilder.forward(distance, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder forward(
|
||||
double distance,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.forward(distance, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder back(double distance) {
|
||||
return addPath(() -> currentTrajectoryBuilder.back(distance, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder back(
|
||||
double distance,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.back(distance, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder strafeLeft(double distance) {
|
||||
return addPath(() -> currentTrajectoryBuilder.strafeLeft(distance, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder strafeLeft(
|
||||
double distance,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.strafeLeft(distance, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder strafeRight(double distance) {
|
||||
return addPath(() -> currentTrajectoryBuilder.strafeRight(distance, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder strafeRight(
|
||||
double distance,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.strafeRight(distance, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineTo(Vector2d endPosition, double endHeading) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineTo(endPosition, endHeading, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineTo(
|
||||
Vector2d endPosition,
|
||||
double endHeading,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineTo(endPosition, endHeading, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineToConstantHeading(Vector2d endPosition, double endHeading) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineToConstantHeading(endPosition, endHeading, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineToConstantHeading(
|
||||
Vector2d endPosition,
|
||||
double endHeading,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineToConstantHeading(endPosition, endHeading, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineToLinearHeading(Pose2d endPose, double endHeading) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineToLinearHeading(endPose, endHeading, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineToLinearHeading(
|
||||
Pose2d endPose,
|
||||
double endHeading,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineToLinearHeading(endPose, endHeading, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineToSplineHeading(Pose2d endPose, double endHeading) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineToSplineHeading(endPose, endHeading, currentVelConstraint, currentAccelConstraint));
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder splineToSplineHeading(
|
||||
Pose2d endPose,
|
||||
double endHeading,
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
return addPath(() -> currentTrajectoryBuilder.splineToSplineHeading(endPose, endHeading, velConstraint, accelConstraint));
|
||||
}
|
||||
|
||||
private TrajectorySequenceBuilder addPath(AddPathCallback callback) {
|
||||
if (currentTrajectoryBuilder == null) newPath();
|
||||
|
||||
try {
|
||||
callback.run();
|
||||
} catch (PathContinuityViolationException e) {
|
||||
newPath();
|
||||
callback.run();
|
||||
}
|
||||
|
||||
Trajectory builtTraj = currentTrajectoryBuilder.build();
|
||||
|
||||
double durationDifference = builtTraj.duration() - lastDurationTraj;
|
||||
double displacementDifference = builtTraj.getPath().length() - lastDisplacementTraj;
|
||||
|
||||
lastPose = builtTraj.end();
|
||||
currentDuration += durationDifference;
|
||||
currentDisplacement += displacementDifference;
|
||||
|
||||
lastDurationTraj = builtTraj.duration();
|
||||
lastDisplacementTraj = builtTraj.getPath().length();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder setTangent(double tangent) {
|
||||
setAbsoluteTangent = true;
|
||||
absoluteTangent = tangent;
|
||||
|
||||
pushPath();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private TrajectorySequenceBuilder setTangentOffset(double offset) {
|
||||
setAbsoluteTangent = false;
|
||||
|
||||
this.tangentOffset = offset;
|
||||
this.pushPath();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder setReversed(boolean reversed) {
|
||||
return reversed ? this.setTangentOffset(Math.toRadians(180.0)) : this.setTangentOffset(0.0);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder setConstraints(
|
||||
TrajectoryVelocityConstraint velConstraint,
|
||||
TrajectoryAccelerationConstraint accelConstraint
|
||||
) {
|
||||
this.currentVelConstraint = velConstraint;
|
||||
this.currentAccelConstraint = accelConstraint;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder resetConstraints() {
|
||||
this.currentVelConstraint = this.baseVelConstraint;
|
||||
this.currentAccelConstraint = this.baseAccelConstraint;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder setVelConstraint(TrajectoryVelocityConstraint velConstraint) {
|
||||
this.currentVelConstraint = velConstraint;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder resetVelConstraint() {
|
||||
this.currentVelConstraint = this.baseVelConstraint;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder setAccelConstraint(TrajectoryAccelerationConstraint accelConstraint) {
|
||||
this.currentAccelConstraint = accelConstraint;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder resetAccelConstraint() {
|
||||
this.currentAccelConstraint = this.baseAccelConstraint;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder setTurnConstraint(double maxAngVel, double maxAngAccel) {
|
||||
this.currentTurnConstraintMaxAngVel = maxAngVel;
|
||||
this.currentTurnConstraintMaxAngAccel = maxAngAccel;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder resetTurnConstraint() {
|
||||
this.currentTurnConstraintMaxAngVel = baseTurnConstraintMaxAngVel;
|
||||
this.currentTurnConstraintMaxAngAccel = baseTurnConstraintMaxAngAccel;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addTemporalMarker(MarkerCallback callback) {
|
||||
return this.addTemporalMarker(currentDuration, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder UNSTABLE_addTemporalMarkerOffset(double offset, MarkerCallback callback) {
|
||||
return this.addTemporalMarker(currentDuration + offset, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addTemporalMarker(double time, MarkerCallback callback) {
|
||||
return this.addTemporalMarker(0.0, time, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addTemporalMarker(double scale, double offset, MarkerCallback callback) {
|
||||
return this.addTemporalMarker(time -> scale * time + offset, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addTemporalMarker(TimeProducer time, MarkerCallback callback) {
|
||||
this.temporalMarkers.add(new TemporalMarker(time, callback));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addSpatialMarker(Vector2d point, MarkerCallback callback) {
|
||||
this.spatialMarkers.add(new SpatialMarker(point, callback));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addDisplacementMarker(MarkerCallback callback) {
|
||||
return this.addDisplacementMarker(currentDisplacement, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder UNSTABLE_addDisplacementMarkerOffset(double offset, MarkerCallback callback) {
|
||||
return this.addDisplacementMarker(currentDisplacement + offset, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addDisplacementMarker(double displacement, MarkerCallback callback) {
|
||||
return this.addDisplacementMarker(0.0, displacement, callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addDisplacementMarker(double scale, double offset, MarkerCallback callback) {
|
||||
return addDisplacementMarker((displacement -> scale * displacement + offset), callback);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addDisplacementMarker(DisplacementProducer displacement, MarkerCallback callback) {
|
||||
displacementMarkers.add(new DisplacementMarker(displacement, callback));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder turn(double angle) {
|
||||
return turn(angle, currentTurnConstraintMaxAngVel, currentTurnConstraintMaxAngAccel);
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder turn(double angle, double maxAngVel, double maxAngAccel) {
|
||||
pushPath();
|
||||
|
||||
MotionProfile turnProfile = MotionProfileGenerator.generateSimpleMotionProfile(
|
||||
new MotionState(lastPose.getHeading(), 0.0, 0.0, 0.0),
|
||||
new MotionState(lastPose.getHeading() + angle, 0.0, 0.0, 0.0),
|
||||
maxAngVel,
|
||||
maxAngAccel
|
||||
);
|
||||
|
||||
sequenceSegments.add(new TurnSegment(lastPose, angle, turnProfile, Collections.emptyList()));
|
||||
|
||||
lastPose = new Pose2d(
|
||||
lastPose.getX(), lastPose.getY(),
|
||||
Angle.norm(lastPose.getHeading() + angle)
|
||||
);
|
||||
|
||||
currentDuration += turnProfile.duration();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder waitSeconds(double seconds) {
|
||||
pushPath();
|
||||
sequenceSegments.add(new WaitSegment(lastPose, seconds, Collections.emptyList()));
|
||||
|
||||
currentDuration += seconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrajectorySequenceBuilder addTrajectory(Trajectory trajectory) {
|
||||
pushPath();
|
||||
|
||||
sequenceSegments.add(new TrajectorySegment(trajectory));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void pushPath() {
|
||||
if (currentTrajectoryBuilder != null) {
|
||||
Trajectory builtTraj = currentTrajectoryBuilder.build();
|
||||
sequenceSegments.add(new TrajectorySegment(builtTraj));
|
||||
}
|
||||
|
||||
currentTrajectoryBuilder = null;
|
||||
}
|
||||
|
||||
private void newPath() {
|
||||
if (currentTrajectoryBuilder != null)
|
||||
pushPath();
|
||||
|
||||
lastDurationTraj = 0.0;
|
||||
lastDisplacementTraj = 0.0;
|
||||
|
||||
double tangent = setAbsoluteTangent ? absoluteTangent : Angle.norm(lastPose.getHeading() + tangentOffset);
|
||||
|
||||
currentTrajectoryBuilder = new TrajectoryBuilder(lastPose, tangent, currentVelConstraint, currentAccelConstraint, resolution);
|
||||
}
|
||||
|
||||
public TrajectorySequence build() {
|
||||
pushPath();
|
||||
|
||||
List<TrajectoryMarker> globalMarkers = convertMarkersToGlobal(
|
||||
sequenceSegments,
|
||||
temporalMarkers, displacementMarkers, spatialMarkers
|
||||
);
|
||||
projectGlobalMarkersToLocalSegments(globalMarkers, sequenceSegments);
|
||||
|
||||
return new TrajectorySequence(sequenceSegments);
|
||||
}
|
||||
|
||||
private List<TrajectoryMarker> convertMarkersToGlobal(
|
||||
List<SequenceSegment> sequenceSegments,
|
||||
List<TemporalMarker> temporalMarkers,
|
||||
List<DisplacementMarker> displacementMarkers,
|
||||
List<SpatialMarker> spatialMarkers
|
||||
) {
|
||||
ArrayList<TrajectoryMarker> trajectoryMarkers = new ArrayList<>();
|
||||
|
||||
// Convert temporal markers
|
||||
for (TemporalMarker marker : temporalMarkers) {
|
||||
trajectoryMarkers.add(
|
||||
new TrajectoryMarker(marker.getProducer().produce(currentDuration), marker.getCallback())
|
||||
);
|
||||
}
|
||||
|
||||
// Convert displacement markers
|
||||
for (DisplacementMarker marker : displacementMarkers) {
|
||||
double time = displacementToTime(
|
||||
sequenceSegments,
|
||||
marker.getProducer().produce(currentDisplacement)
|
||||
);
|
||||
|
||||
trajectoryMarkers.add(
|
||||
new TrajectoryMarker(
|
||||
time,
|
||||
marker.getCallback()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Convert spatial markers
|
||||
for (SpatialMarker marker : spatialMarkers) {
|
||||
trajectoryMarkers.add(
|
||||
new TrajectoryMarker(
|
||||
pointToTime(sequenceSegments, marker.getPoint()),
|
||||
marker.getCallback()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return trajectoryMarkers;
|
||||
}
|
||||
|
||||
private void projectGlobalMarkersToLocalSegments(List<TrajectoryMarker> markers, List<SequenceSegment> sequenceSegments) {
|
||||
if (sequenceSegments.isEmpty()) return;
|
||||
|
||||
markers.sort(Comparator.comparingDouble(TrajectoryMarker::getTime));
|
||||
|
||||
double timeOffset = 0.0;
|
||||
int markerIndex = 0;
|
||||
for (SequenceSegment segment : sequenceSegments) {
|
||||
while (markerIndex < markers.size()) {
|
||||
TrajectoryMarker marker = markers.get(markerIndex);
|
||||
if (marker.getTime() >= timeOffset + segment.getDuration()) {
|
||||
break;
|
||||
}
|
||||
|
||||
segment.getMarkers().add(new TrajectoryMarker(
|
||||
Math.max(0.0, marker.getTime()) - timeOffset, marker.getCallback()));
|
||||
++markerIndex;
|
||||
}
|
||||
|
||||
timeOffset += segment.getDuration();
|
||||
}
|
||||
|
||||
SequenceSegment segment = sequenceSegments.get(sequenceSegments.size() - 1);
|
||||
while (markerIndex < markers.size()) {
|
||||
TrajectoryMarker marker = markers.get(markerIndex);
|
||||
segment.getMarkers().add(new TrajectoryMarker(segment.getDuration(), marker.getCallback()));
|
||||
++markerIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from Road Runner's TrajectoryGenerator.displacementToTime() since it's private
|
||||
// note: this assumes that the profile position is monotonic increasing
|
||||
private Double motionProfileDisplacementToTime(MotionProfile profile, double s) {
|
||||
double tLo = 0.0;
|
||||
double tHi = profile.duration();
|
||||
while (!(Math.abs(tLo - tHi) < 1e-6)) {
|
||||
double tMid = 0.5 * (tLo + tHi);
|
||||
if (profile.get(tMid).getX() > s) {
|
||||
tHi = tMid;
|
||||
} else {
|
||||
tLo = tMid;
|
||||
}
|
||||
}
|
||||
return 0.5 * (tLo + tHi);
|
||||
}
|
||||
|
||||
private Double displacementToTime(List<SequenceSegment> sequenceSegments, double s) {
|
||||
double currentTime = 0.0;
|
||||
double currentDisplacement = 0.0;
|
||||
|
||||
for (SequenceSegment segment : sequenceSegments) {
|
||||
if (segment instanceof TrajectorySegment) {
|
||||
TrajectorySegment thisSegment = (TrajectorySegment) segment;
|
||||
|
||||
double segmentLength = thisSegment.getTrajectory().getPath().length();
|
||||
|
||||
if (currentDisplacement + segmentLength > s) {
|
||||
double target = s - currentDisplacement;
|
||||
double timeInSegment = motionProfileDisplacementToTime(
|
||||
thisSegment.getTrajectory().getProfile(),
|
||||
target
|
||||
);
|
||||
|
||||
return currentTime + timeInSegment;
|
||||
} else {
|
||||
currentDisplacement += segmentLength;
|
||||
}
|
||||
}
|
||||
|
||||
currentTime += segment.getDuration();
|
||||
}
|
||||
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
private Double pointToTime(List<SequenceSegment> sequenceSegments, Vector2d point) {
|
||||
class ComparingPoints {
|
||||
private final double distanceToPoint;
|
||||
private final double totalDisplacement;
|
||||
private final double thisPathDisplacement;
|
||||
|
||||
public ComparingPoints(double distanceToPoint, double totalDisplacement, double thisPathDisplacement) {
|
||||
this.distanceToPoint = distanceToPoint;
|
||||
this.totalDisplacement = totalDisplacement;
|
||||
this.thisPathDisplacement = thisPathDisplacement;
|
||||
}
|
||||
}
|
||||
|
||||
List<ComparingPoints> projectedPoints = new ArrayList<>();
|
||||
|
||||
for (SequenceSegment segment : sequenceSegments) {
|
||||
if (segment instanceof TrajectorySegment) {
|
||||
TrajectorySegment thisSegment = (TrajectorySegment) segment;
|
||||
|
||||
double displacement = thisSegment.getTrajectory().getPath().project(point, 0.25);
|
||||
Vector2d projectedPoint = thisSegment.getTrajectory().getPath().get(displacement).vec();
|
||||
double distanceToPoint = point.minus(projectedPoint).norm();
|
||||
|
||||
double totalDisplacement = 0.0;
|
||||
|
||||
for (ComparingPoints comparingPoint : projectedPoints) {
|
||||
totalDisplacement += comparingPoint.totalDisplacement;
|
||||
}
|
||||
|
||||
totalDisplacement += displacement;
|
||||
|
||||
projectedPoints.add(new ComparingPoints(distanceToPoint, displacement, totalDisplacement));
|
||||
}
|
||||
}
|
||||
|
||||
ComparingPoints closestPoint = null;
|
||||
|
||||
for (ComparingPoints comparingPoint : projectedPoints) {
|
||||
if (closestPoint == null) {
|
||||
closestPoint = comparingPoint;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (comparingPoint.distanceToPoint < closestPoint.distanceToPoint)
|
||||
closestPoint = comparingPoint;
|
||||
}
|
||||
|
||||
return displacementToTime(sequenceSegments, closestPoint.thisPathDisplacement);
|
||||
}
|
||||
|
||||
private interface AddPathCallback {
|
||||
void run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.canvas.Canvas;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
|
||||
import com.acmerobotics.roadrunner.control.PIDCoefficients;
|
||||
import com.acmerobotics.roadrunner.control.PIDFController;
|
||||
import com.acmerobotics.roadrunner.drive.DriveSignal;
|
||||
import com.acmerobotics.roadrunner.followers.TrajectoryFollower;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.profile.MotionState;
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryMarker;
|
||||
import com.acmerobotics.roadrunner.util.NanoClock;
|
||||
import com.qualcomm.robotcore.hardware.VoltageSensor;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.SequenceSegment;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.TrajectorySegment;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.TurnSegment;
|
||||
import org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment.WaitSegment;
|
||||
import org.firstinspires.ftc.teamcode.util.DashboardUtil;
|
||||
import org.firstinspires.ftc.teamcode.util.LogFiles;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Config
|
||||
public class TrajectorySequenceRunner {
|
||||
public static String COLOR_INACTIVE_TRAJECTORY = "#4caf507a";
|
||||
public static String COLOR_INACTIVE_TURN = "#7c4dff7a";
|
||||
public static String COLOR_INACTIVE_WAIT = "#dd2c007a";
|
||||
|
||||
public static String COLOR_ACTIVE_TRAJECTORY = "#4CAF50";
|
||||
public static String COLOR_ACTIVE_TURN = "#7c4dff";
|
||||
public static String COLOR_ACTIVE_WAIT = "#dd2c00";
|
||||
|
||||
public static int POSE_HISTORY_LIMIT = 100;
|
||||
|
||||
private final TrajectoryFollower follower;
|
||||
|
||||
private final PIDFController turnController;
|
||||
|
||||
private final NanoClock clock;
|
||||
|
||||
private TrajectorySequence currentTrajectorySequence;
|
||||
private double currentSegmentStartTime;
|
||||
private int currentSegmentIndex;
|
||||
private int lastSegmentIndex;
|
||||
|
||||
private Pose2d lastPoseError = new Pose2d();
|
||||
|
||||
List<TrajectoryMarker> remainingMarkers = new ArrayList<>();
|
||||
|
||||
private final FtcDashboard dashboard;
|
||||
private final LinkedList<Pose2d> poseHistory = new LinkedList<>();
|
||||
|
||||
private VoltageSensor voltageSensor;
|
||||
|
||||
private List<Integer> lastDriveEncPositions, lastDriveEncVels, lastTrackingEncPositions, lastTrackingEncVels;
|
||||
|
||||
public TrajectorySequenceRunner(
|
||||
TrajectoryFollower follower, PIDCoefficients headingPIDCoefficients, VoltageSensor voltageSensor,
|
||||
List<Integer> lastDriveEncPositions, List<Integer> lastDriveEncVels, List<Integer> lastTrackingEncPositions, List<Integer> lastTrackingEncVels
|
||||
) {
|
||||
this.follower = follower;
|
||||
|
||||
turnController = new PIDFController(headingPIDCoefficients);
|
||||
turnController.setInputBounds(0, 2 * Math.PI);
|
||||
|
||||
this.voltageSensor = voltageSensor;
|
||||
|
||||
this.lastDriveEncPositions = lastDriveEncPositions;
|
||||
this.lastDriveEncVels = lastDriveEncVels;
|
||||
this.lastTrackingEncPositions = lastTrackingEncPositions;
|
||||
this.lastTrackingEncVels = lastTrackingEncVels;
|
||||
|
||||
clock = NanoClock.system();
|
||||
|
||||
dashboard = FtcDashboard.getInstance();
|
||||
dashboard.setTelemetryTransmissionInterval(25);
|
||||
}
|
||||
|
||||
public void followTrajectorySequenceAsync(TrajectorySequence trajectorySequence) {
|
||||
currentTrajectorySequence = trajectorySequence;
|
||||
currentSegmentStartTime = clock.seconds();
|
||||
currentSegmentIndex = 0;
|
||||
lastSegmentIndex = -1;
|
||||
}
|
||||
|
||||
public @Nullable
|
||||
DriveSignal update(Pose2d poseEstimate, Pose2d poseVelocity) {
|
||||
Pose2d targetPose = null;
|
||||
DriveSignal driveSignal = null;
|
||||
|
||||
TelemetryPacket packet = new TelemetryPacket();
|
||||
Canvas fieldOverlay = packet.fieldOverlay();
|
||||
|
||||
SequenceSegment currentSegment = null;
|
||||
|
||||
if (currentTrajectorySequence != null) {
|
||||
if (currentSegmentIndex >= currentTrajectorySequence.size()) {
|
||||
for (TrajectoryMarker marker : remainingMarkers) {
|
||||
marker.getCallback().onMarkerReached();
|
||||
}
|
||||
|
||||
remainingMarkers.clear();
|
||||
|
||||
currentTrajectorySequence = null;
|
||||
}
|
||||
|
||||
if (currentTrajectorySequence == null)
|
||||
return new DriveSignal();
|
||||
|
||||
double now = clock.seconds();
|
||||
boolean isNewTransition = currentSegmentIndex != lastSegmentIndex;
|
||||
|
||||
currentSegment = currentTrajectorySequence.get(currentSegmentIndex);
|
||||
|
||||
if (isNewTransition) {
|
||||
currentSegmentStartTime = now;
|
||||
lastSegmentIndex = currentSegmentIndex;
|
||||
|
||||
for (TrajectoryMarker marker : remainingMarkers) {
|
||||
marker.getCallback().onMarkerReached();
|
||||
}
|
||||
|
||||
remainingMarkers.clear();
|
||||
|
||||
remainingMarkers.addAll(currentSegment.getMarkers());
|
||||
Collections.sort(remainingMarkers, (t1, t2) -> Double.compare(t1.getTime(), t2.getTime()));
|
||||
}
|
||||
|
||||
double deltaTime = now - currentSegmentStartTime;
|
||||
|
||||
if (currentSegment instanceof TrajectorySegment) {
|
||||
Trajectory currentTrajectory = ((TrajectorySegment) currentSegment).getTrajectory();
|
||||
|
||||
if (isNewTransition)
|
||||
follower.followTrajectory(currentTrajectory);
|
||||
|
||||
if (!follower.isFollowing()) {
|
||||
currentSegmentIndex++;
|
||||
|
||||
driveSignal = new DriveSignal();
|
||||
} else {
|
||||
driveSignal = follower.update(poseEstimate, poseVelocity);
|
||||
lastPoseError = follower.getLastError();
|
||||
}
|
||||
|
||||
targetPose = currentTrajectory.get(deltaTime);
|
||||
} else if (currentSegment instanceof TurnSegment) {
|
||||
MotionState targetState = ((TurnSegment) currentSegment).getMotionProfile().get(deltaTime);
|
||||
|
||||
turnController.setTargetPosition(targetState.getX());
|
||||
|
||||
double correction = turnController.update(poseEstimate.getHeading());
|
||||
|
||||
double targetOmega = targetState.getV();
|
||||
double targetAlpha = targetState.getA();
|
||||
|
||||
lastPoseError = new Pose2d(0, 0, turnController.getLastError());
|
||||
|
||||
Pose2d startPose = currentSegment.getStartPose();
|
||||
targetPose = startPose.copy(startPose.getX(), startPose.getY(), targetState.getX());
|
||||
|
||||
driveSignal = new DriveSignal(
|
||||
new Pose2d(0, 0, targetOmega + correction),
|
||||
new Pose2d(0, 0, targetAlpha)
|
||||
);
|
||||
|
||||
if (deltaTime >= currentSegment.getDuration()) {
|
||||
currentSegmentIndex++;
|
||||
driveSignal = new DriveSignal();
|
||||
}
|
||||
} else if (currentSegment instanceof WaitSegment) {
|
||||
lastPoseError = new Pose2d();
|
||||
|
||||
targetPose = currentSegment.getStartPose();
|
||||
driveSignal = new DriveSignal();
|
||||
|
||||
if (deltaTime >= currentSegment.getDuration()) {
|
||||
currentSegmentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
while (remainingMarkers.size() > 0 && deltaTime > remainingMarkers.get(0).getTime()) {
|
||||
remainingMarkers.get(0).getCallback().onMarkerReached();
|
||||
remainingMarkers.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
poseHistory.add(poseEstimate);
|
||||
|
||||
if (POSE_HISTORY_LIMIT > -1 && poseHistory.size() > POSE_HISTORY_LIMIT) {
|
||||
poseHistory.removeFirst();
|
||||
}
|
||||
|
||||
final double NOMINAL_VOLTAGE = 12.0;
|
||||
double voltage = voltageSensor.getVoltage();
|
||||
if (driveSignal != null && !DriveConstants.RUN_USING_ENCODER) {
|
||||
driveSignal = new DriveSignal(
|
||||
driveSignal.getVel().times(NOMINAL_VOLTAGE / voltage),
|
||||
driveSignal.getAccel().times(NOMINAL_VOLTAGE / voltage)
|
||||
);
|
||||
}
|
||||
|
||||
if (targetPose != null) {
|
||||
LogFiles.record(
|
||||
targetPose, poseEstimate, voltage,
|
||||
lastDriveEncPositions, lastDriveEncVels, lastTrackingEncPositions, lastTrackingEncVels
|
||||
);
|
||||
}
|
||||
|
||||
packet.put("x", poseEstimate.getX());
|
||||
packet.put("y", poseEstimate.getY());
|
||||
packet.put("heading (deg)", Math.toDegrees(poseEstimate.getHeading()));
|
||||
|
||||
packet.put("xError", getLastPoseError().getX());
|
||||
packet.put("yError", getLastPoseError().getY());
|
||||
packet.put("headingError (deg)", Math.toDegrees(getLastPoseError().getHeading()));
|
||||
|
||||
draw(fieldOverlay, currentTrajectorySequence, currentSegment, targetPose, poseEstimate);
|
||||
|
||||
dashboard.sendTelemetryPacket(packet);
|
||||
|
||||
return driveSignal;
|
||||
}
|
||||
|
||||
private void draw(
|
||||
Canvas fieldOverlay,
|
||||
TrajectorySequence sequence, SequenceSegment currentSegment,
|
||||
Pose2d targetPose, Pose2d poseEstimate
|
||||
) {
|
||||
if (sequence != null) {
|
||||
for (int i = 0; i < sequence.size(); i++) {
|
||||
SequenceSegment segment = sequence.get(i);
|
||||
|
||||
if (segment instanceof TrajectorySegment) {
|
||||
fieldOverlay.setStrokeWidth(1);
|
||||
fieldOverlay.setStroke(COLOR_INACTIVE_TRAJECTORY);
|
||||
|
||||
DashboardUtil.drawSampledPath(fieldOverlay, ((TrajectorySegment) segment).getTrajectory().getPath());
|
||||
} else if (segment instanceof TurnSegment) {
|
||||
Pose2d pose = segment.getStartPose();
|
||||
|
||||
fieldOverlay.setFill(COLOR_INACTIVE_TURN);
|
||||
fieldOverlay.fillCircle(pose.getX(), pose.getY(), 2);
|
||||
} else if (segment instanceof WaitSegment) {
|
||||
Pose2d pose = segment.getStartPose();
|
||||
|
||||
fieldOverlay.setStrokeWidth(1);
|
||||
fieldOverlay.setStroke(COLOR_INACTIVE_WAIT);
|
||||
fieldOverlay.strokeCircle(pose.getX(), pose.getY(), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSegment != null) {
|
||||
if (currentSegment instanceof TrajectorySegment) {
|
||||
Trajectory currentTrajectory = ((TrajectorySegment) currentSegment).getTrajectory();
|
||||
|
||||
fieldOverlay.setStrokeWidth(1);
|
||||
fieldOverlay.setStroke(COLOR_ACTIVE_TRAJECTORY);
|
||||
|
||||
DashboardUtil.drawSampledPath(fieldOverlay, currentTrajectory.getPath());
|
||||
} else if (currentSegment instanceof TurnSegment) {
|
||||
Pose2d pose = currentSegment.getStartPose();
|
||||
|
||||
fieldOverlay.setFill(COLOR_ACTIVE_TURN);
|
||||
fieldOverlay.fillCircle(pose.getX(), pose.getY(), 3);
|
||||
} else if (currentSegment instanceof WaitSegment) {
|
||||
Pose2d pose = currentSegment.getStartPose();
|
||||
|
||||
fieldOverlay.setStrokeWidth(1);
|
||||
fieldOverlay.setStroke(COLOR_ACTIVE_WAIT);
|
||||
fieldOverlay.strokeCircle(pose.getX(), pose.getY(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetPose != null) {
|
||||
fieldOverlay.setStrokeWidth(1);
|
||||
fieldOverlay.setStroke("#4CAF50");
|
||||
DashboardUtil.drawRobot(fieldOverlay, targetPose);
|
||||
}
|
||||
|
||||
fieldOverlay.setStroke("#3F51B5");
|
||||
DashboardUtil.drawPoseHistory(fieldOverlay, poseHistory);
|
||||
|
||||
fieldOverlay.setStroke("#3F51B5");
|
||||
DashboardUtil.drawRobot(fieldOverlay, poseEstimate);
|
||||
}
|
||||
|
||||
public Pose2d getLastPoseError() {
|
||||
return lastPoseError;
|
||||
}
|
||||
|
||||
public boolean isBusy() {
|
||||
return currentTrajectorySequence != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryMarker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SequenceSegment {
|
||||
private final double duration;
|
||||
private final Pose2d startPose;
|
||||
private final Pose2d endPose;
|
||||
private final List<TrajectoryMarker> markers;
|
||||
|
||||
protected SequenceSegment(
|
||||
double duration,
|
||||
Pose2d startPose, Pose2d endPose,
|
||||
List<TrajectoryMarker> markers
|
||||
) {
|
||||
this.duration = duration;
|
||||
this.startPose = startPose;
|
||||
this.endPose = endPose;
|
||||
this.markers = new ArrayList<>(markers);
|
||||
}
|
||||
|
||||
public double getDuration() {
|
||||
return this.duration;
|
||||
}
|
||||
|
||||
public Pose2d getStartPose() {
|
||||
return startPose;
|
||||
}
|
||||
|
||||
public Pose2d getEndPose() {
|
||||
return endPose;
|
||||
}
|
||||
|
||||
public List<TrajectoryMarker> getMarkers() {
|
||||
return markers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment;
|
||||
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public final class TrajectorySegment extends SequenceSegment {
|
||||
private final Trajectory trajectory;
|
||||
|
||||
public TrajectorySegment(Trajectory trajectory) {
|
||||
// Note: Markers are already stored in the `Trajectory` itself.
|
||||
// This class should not hold any markers
|
||||
super(trajectory.duration(), trajectory.start(), trajectory.end(), Collections.emptyList());
|
||||
this.trajectory = trajectory;
|
||||
}
|
||||
|
||||
public Trajectory getTrajectory() {
|
||||
return this.trajectory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.profile.MotionProfile;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryMarker;
|
||||
import com.acmerobotics.roadrunner.util.Angle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class TurnSegment extends SequenceSegment {
|
||||
private final double totalRotation;
|
||||
private final MotionProfile motionProfile;
|
||||
|
||||
public TurnSegment(Pose2d startPose, double totalRotation, MotionProfile motionProfile, List<TrajectoryMarker> markers) {
|
||||
super(
|
||||
motionProfile.duration(),
|
||||
startPose,
|
||||
new Pose2d(
|
||||
startPose.getX(), startPose.getY(),
|
||||
Angle.norm(startPose.getHeading() + totalRotation)
|
||||
),
|
||||
markers
|
||||
);
|
||||
|
||||
this.totalRotation = totalRotation;
|
||||
this.motionProfile = motionProfile;
|
||||
}
|
||||
|
||||
public final double getTotalRotation() {
|
||||
return this.totalRotation;
|
||||
}
|
||||
|
||||
public final MotionProfile getMotionProfile() {
|
||||
return this.motionProfile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.firstinspires.ftc.teamcode.trajectorysequence.sequencesegment;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryMarker;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class WaitSegment extends SequenceSegment {
|
||||
public WaitSegment(Pose2d pose, double seconds, List<TrajectoryMarker> markers) {
|
||||
super(seconds, pose, pose, markers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.acmerobotics.roadrunner.trajectory.Trajectory;
|
||||
import com.acmerobotics.roadrunner.trajectory.TrajectoryBuilder;
|
||||
import com.acmerobotics.roadrunner.trajectory.config.TrajectoryConfig;
|
||||
import com.acmerobotics.roadrunner.trajectory.config.TrajectoryConfigManager;
|
||||
import com.acmerobotics.roadrunner.trajectory.config.TrajectoryGroupConfig;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.internal.system.AppUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Set of utilities for loading trajectories from assets (the plugin save location).
|
||||
*/
|
||||
public class AssetsTrajectoryManager {
|
||||
|
||||
/**
|
||||
* Loads the group config.
|
||||
*/
|
||||
public static @Nullable
|
||||
TrajectoryGroupConfig loadGroupConfig() {
|
||||
try {
|
||||
InputStream inputStream = AppUtil.getDefContext().getAssets().open(
|
||||
"trajectory/" + TrajectoryConfigManager.GROUP_FILENAME);
|
||||
return TrajectoryConfigManager.loadGroupConfig(inputStream);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a trajectory config with the given name.
|
||||
*/
|
||||
public static @Nullable TrajectoryConfig loadConfig(String name) {
|
||||
try {
|
||||
InputStream inputStream = AppUtil.getDefContext().getAssets().open(
|
||||
"trajectory/" + name + ".yaml");
|
||||
return TrajectoryConfigManager.loadConfig(inputStream);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a trajectory builder with the given name.
|
||||
*/
|
||||
public static @Nullable TrajectoryBuilder loadBuilder(String name) {
|
||||
TrajectoryGroupConfig groupConfig = loadGroupConfig();
|
||||
TrajectoryConfig config = loadConfig(name);
|
||||
if (groupConfig == null || config == null) {
|
||||
return null;
|
||||
}
|
||||
return config.toTrajectoryBuilder(groupConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a trajectory with the given name.
|
||||
*/
|
||||
public static @Nullable Trajectory load(String name) {
|
||||
TrajectoryBuilder builder = loadBuilder(name);
|
||||
if (builder == null) {
|
||||
return null;
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
/**
|
||||
* IMU axes signs in the order XYZ (after remapping).
|
||||
*/
|
||||
public enum AxesSigns {
|
||||
PPP(0b000),
|
||||
PPN(0b001),
|
||||
PNP(0b010),
|
||||
PNN(0b011),
|
||||
NPP(0b100),
|
||||
NPN(0b101),
|
||||
NNP(0b110),
|
||||
NNN(0b111);
|
||||
|
||||
public final int bVal;
|
||||
|
||||
AxesSigns(int bVal) {
|
||||
this.bVal = bVal;
|
||||
}
|
||||
|
||||
public static AxesSigns fromBinaryValue(int bVal) {
|
||||
int maskedVal = bVal & 0x07;
|
||||
switch (maskedVal) {
|
||||
case 0b000:
|
||||
return AxesSigns.PPP;
|
||||
case 0b001:
|
||||
return AxesSigns.PPN;
|
||||
case 0b010:
|
||||
return AxesSigns.PNP;
|
||||
case 0b011:
|
||||
return AxesSigns.PNN;
|
||||
case 0b100:
|
||||
return AxesSigns.NPP;
|
||||
case 0b101:
|
||||
return AxesSigns.NPN;
|
||||
case 0b110:
|
||||
return AxesSigns.NNP;
|
||||
case 0b111:
|
||||
return AxesSigns.NNN;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected value for maskedVal: " + maskedVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
/**
|
||||
* A direction for an axis to be remapped to
|
||||
*/
|
||||
public enum AxisDirection {
|
||||
POS_X, NEG_X, POS_Y, NEG_Y, POS_Z, NEG_Z
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import com.acmerobotics.dashboard.canvas.Canvas;
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.acmerobotics.roadrunner.geometry.Vector2d;
|
||||
import com.acmerobotics.roadrunner.path.Path;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Set of helper functions for drawing Road Runner paths and trajectories on dashboard canvases.
|
||||
*/
|
||||
public class DashboardUtil {
|
||||
private static final double DEFAULT_RESOLUTION = 2.0; // distance units; presumed inches
|
||||
private static final double ROBOT_RADIUS = 9; // in
|
||||
|
||||
|
||||
public static void drawPoseHistory(Canvas canvas, List<Pose2d> poseHistory) {
|
||||
double[] xPoints = new double[poseHistory.size()];
|
||||
double[] yPoints = new double[poseHistory.size()];
|
||||
for (int i = 0; i < poseHistory.size(); i++) {
|
||||
Pose2d pose = poseHistory.get(i);
|
||||
xPoints[i] = pose.getX();
|
||||
yPoints[i] = pose.getY();
|
||||
}
|
||||
canvas.strokePolyline(xPoints, yPoints);
|
||||
}
|
||||
|
||||
public static void drawSampledPath(Canvas canvas, Path path, double resolution) {
|
||||
int samples = (int) Math.ceil(path.length() / resolution);
|
||||
double[] xPoints = new double[samples];
|
||||
double[] yPoints = new double[samples];
|
||||
double dx = path.length() / (samples - 1);
|
||||
for (int i = 0; i < samples; i++) {
|
||||
double displacement = i * dx;
|
||||
Pose2d pose = path.get(displacement);
|
||||
xPoints[i] = pose.getX();
|
||||
yPoints[i] = pose.getY();
|
||||
}
|
||||
canvas.strokePolyline(xPoints, yPoints);
|
||||
}
|
||||
|
||||
public static void drawSampledPath(Canvas canvas, Path path) {
|
||||
drawSampledPath(canvas, path, DEFAULT_RESOLUTION);
|
||||
}
|
||||
|
||||
public static void drawRobot(Canvas canvas, Pose2d pose) {
|
||||
canvas.strokeCircle(pose.getX(), pose.getY(), ROBOT_RADIUS);
|
||||
Vector2d v = pose.headingVec().times(ROBOT_RADIUS);
|
||||
double x1 = pose.getX() + v.getX() / 2, y1 = pose.getY() + v.getY() / 2;
|
||||
double x2 = pose.getX() + v.getX(), y2 = pose.getY() + v.getY();
|
||||
canvas.strokeLine(x1, y1, x2, y2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import com.acmerobotics.roadrunner.util.NanoClock;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
|
||||
/**
|
||||
* Wraps a motor instance to provide corrected velocity counts and allow reversing independently of the corresponding
|
||||
* slot's motor direction
|
||||
*/
|
||||
public class Encoder {
|
||||
private final static int CPS_STEP = 0x10000;
|
||||
|
||||
private static double inverseOverflow(double input, double estimate) {
|
||||
// convert to uint16
|
||||
int real = (int) input & 0xffff;
|
||||
// initial, modulo-based correction: it can recover the remainder of 5 of the upper 16 bits
|
||||
// because the velocity is always a multiple of 20 cps due to Expansion Hub's 50ms measurement window
|
||||
real += ((real % 20) / 4) * CPS_STEP;
|
||||
// estimate-based correction: it finds the nearest multiple of 5 to correct the upper bits by
|
||||
real += Math.round((estimate - real) / (5 * CPS_STEP)) * 5 * CPS_STEP;
|
||||
return real;
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
FORWARD(1),
|
||||
REVERSE(-1);
|
||||
|
||||
private int multiplier;
|
||||
|
||||
Direction(int multiplier) {
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
public int getMultiplier() {
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private DcMotorEx motor;
|
||||
private NanoClock clock;
|
||||
|
||||
private Direction direction;
|
||||
|
||||
private int lastPosition;
|
||||
private int velocityEstimateIdx;
|
||||
private double[] velocityEstimates;
|
||||
private double lastUpdateTime;
|
||||
|
||||
public Encoder(DcMotorEx motor, NanoClock clock) {
|
||||
this.motor = motor;
|
||||
this.clock = clock;
|
||||
|
||||
this.direction = Direction.FORWARD;
|
||||
|
||||
this.lastPosition = 0;
|
||||
this.velocityEstimates = new double[3];
|
||||
this.lastUpdateTime = clock.seconds();
|
||||
}
|
||||
|
||||
public Encoder(DcMotorEx motor) {
|
||||
this(motor, NanoClock.system());
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
private int getMultiplier() {
|
||||
return getDirection().getMultiplier() * (motor.getDirection() == DcMotorSimple.Direction.FORWARD ? 1 : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to set the direction of the counts and velocity without modifying the motor's direction state
|
||||
* @param direction either reverse or forward depending on if encoder counts should be negated
|
||||
*/
|
||||
public void setDirection(Direction direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position from the underlying motor and adjusts for the set direction.
|
||||
* Additionally, this method updates the velocity estimates used for compensated velocity
|
||||
*
|
||||
* @return encoder position
|
||||
*/
|
||||
public int getCurrentPosition() {
|
||||
int multiplier = getMultiplier();
|
||||
int currentPosition = motor.getCurrentPosition() * multiplier;
|
||||
if (currentPosition != lastPosition) {
|
||||
double currentTime = clock.seconds();
|
||||
double dt = currentTime - lastUpdateTime;
|
||||
velocityEstimates[velocityEstimateIdx] = (currentPosition - lastPosition) / dt;
|
||||
velocityEstimateIdx = (velocityEstimateIdx + 1) % 3;
|
||||
lastPosition = currentPosition;
|
||||
lastUpdateTime = currentTime;
|
||||
}
|
||||
return currentPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the velocity directly from the underlying motor and compensates for the direction
|
||||
* See {@link #getCorrectedVelocity} for high (>2^15) counts per second velocities (such as on REV Through Bore)
|
||||
*
|
||||
* @return raw velocity
|
||||
*/
|
||||
public double getRawVelocity() {
|
||||
int multiplier = getMultiplier();
|
||||
return motor.getVelocity() * multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses velocity estimates gathered in {@link #getCurrentPosition} to estimate the upper bits of velocity
|
||||
* that are lost in overflow due to velocity being transmitted as 16 bits.
|
||||
* CAVEAT: must regularly call {@link #getCurrentPosition} for the compensation to work correctly.
|
||||
*
|
||||
* @return corrected velocity
|
||||
*/
|
||||
public double getCorrectedVelocity() {
|
||||
double median = velocityEstimates[0] > velocityEstimates[1]
|
||||
? Math.max(velocityEstimates[1], Math.min(velocityEstimates[0], velocityEstimates[2]))
|
||||
: Math.max(velocityEstimates[0], Math.min(velocityEstimates[1], velocityEstimates[2]));
|
||||
return inverseOverflow(getRawVelocity(), median);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
||||
import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerImpl;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerNotifier;
|
||||
import com.qualcomm.robotcore.util.RobotLog;
|
||||
import com.qualcomm.robotcore.util.WebHandlerManager;
|
||||
|
||||
import org.firstinspires.ftc.ftccommon.external.WebHandlerRegistrar;
|
||||
import org.firstinspires.ftc.robotcore.internal.system.AppUtil;
|
||||
import org.firstinspires.ftc.teamcode.drive.DriveConstants;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.SampleTankDrive;
|
||||
import org.firstinspires.ftc.teamcode.drive.StandardTrackingWheelLocalizer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
|
||||
public final class LogFiles {
|
||||
private static final File ROOT =
|
||||
new File(AppUtil.ROOT_FOLDER + "/RoadRunner/logs/");
|
||||
|
||||
public static LogFile log = new LogFile("uninitialized");
|
||||
|
||||
public static class LogFile {
|
||||
public String version = "quickstart1 v2";
|
||||
|
||||
public String opModeName;
|
||||
public long msInit = System.currentTimeMillis();
|
||||
public long nsInit = System.nanoTime();
|
||||
public long nsStart, nsStop;
|
||||
|
||||
public double ticksPerRev = DriveConstants.TICKS_PER_REV;
|
||||
public double maxRpm = DriveConstants.MAX_RPM;
|
||||
public boolean runUsingEncoder = DriveConstants.RUN_USING_ENCODER;
|
||||
public double motorP = DriveConstants.MOTOR_VELO_PID.p;
|
||||
public double motorI = DriveConstants.MOTOR_VELO_PID.i;
|
||||
public double motorD = DriveConstants.MOTOR_VELO_PID.d;
|
||||
public double motorF = DriveConstants.MOTOR_VELO_PID.f;
|
||||
public double wheelRadius = DriveConstants.WHEEL_RADIUS;
|
||||
public double gearRatio = DriveConstants.GEAR_RATIO;
|
||||
public double trackWidth = DriveConstants.TRACK_WIDTH;
|
||||
public double kV = DriveConstants.kV;
|
||||
public double kA = DriveConstants.kA;
|
||||
public double kStatic = DriveConstants.kStatic;
|
||||
public double maxVel = DriveConstants.MAX_VEL;
|
||||
public double maxAccel = DriveConstants.MAX_ACCEL;
|
||||
public double maxAngVel = DriveConstants.MAX_ANG_VEL;
|
||||
public double maxAngAccel = DriveConstants.MAX_ANG_ACCEL;
|
||||
|
||||
public double mecTransP = SampleMecanumDrive.TRANSLATIONAL_PID.kP;
|
||||
public double mecTransI = SampleMecanumDrive.TRANSLATIONAL_PID.kI;
|
||||
public double mecTransD = SampleMecanumDrive.TRANSLATIONAL_PID.kD;
|
||||
public double mecHeadingP = SampleMecanumDrive.HEADING_PID.kP;
|
||||
public double mecHeadingI = SampleMecanumDrive.HEADING_PID.kI;
|
||||
public double mecHeadingD = SampleMecanumDrive.HEADING_PID.kD;
|
||||
public double mecLateralMultiplier = SampleMecanumDrive.LATERAL_MULTIPLIER;
|
||||
|
||||
public double tankAxialP = SampleTankDrive.AXIAL_PID.kP;
|
||||
public double tankAxialI = SampleTankDrive.AXIAL_PID.kI;
|
||||
public double tankAxialD = SampleTankDrive.AXIAL_PID.kD;
|
||||
public double tankCrossTrackP = SampleTankDrive.CROSS_TRACK_PID.kP;
|
||||
public double tankCrossTrackI = SampleTankDrive.CROSS_TRACK_PID.kI;
|
||||
public double tankCrossTrackD = SampleTankDrive.CROSS_TRACK_PID.kD;
|
||||
public double tankHeadingP = SampleTankDrive.HEADING_PID.kP;
|
||||
public double tankHeadingI = SampleTankDrive.HEADING_PID.kI;
|
||||
public double tankHeadingD = SampleTankDrive.HEADING_PID.kD;
|
||||
|
||||
public double trackingTicksPerRev = StandardTrackingWheelLocalizer.TICKS_PER_REV;
|
||||
public double trackingWheelRadius = StandardTrackingWheelLocalizer.WHEEL_RADIUS;
|
||||
public double trackingGearRatio = StandardTrackingWheelLocalizer.GEAR_RATIO;
|
||||
public double trackingLateralDistance = StandardTrackingWheelLocalizer.LATERAL_DISTANCE;
|
||||
public double trackingForwardOffset = StandardTrackingWheelLocalizer.FORWARD_OFFSET;
|
||||
|
||||
public RevHubOrientationOnRobot.LogoFacingDirection LOGO_FACING_DIR = DriveConstants.LOGO_FACING_DIR;
|
||||
public RevHubOrientationOnRobot.UsbFacingDirection USB_FACING_DIR = DriveConstants.USB_FACING_DIR;
|
||||
|
||||
public List<Long> nsTimes = new ArrayList<>();
|
||||
|
||||
public List<Double> targetXs = new ArrayList<>();
|
||||
public List<Double> targetYs = new ArrayList<>();
|
||||
public List<Double> targetHeadings = new ArrayList<>();
|
||||
|
||||
public List<Double> xs = new ArrayList<>();
|
||||
public List<Double> ys = new ArrayList<>();
|
||||
public List<Double> headings = new ArrayList<>();
|
||||
|
||||
public List<Double> voltages = new ArrayList<>();
|
||||
|
||||
public List<List<Integer>> driveEncPositions = new ArrayList<>();
|
||||
public List<List<Integer>> driveEncVels = new ArrayList<>();
|
||||
public List<List<Integer>> trackingEncPositions = new ArrayList<>();
|
||||
public List<List<Integer>> trackingEncVels = new ArrayList<>();
|
||||
|
||||
public LogFile(String opModeName) {
|
||||
this.opModeName = opModeName;
|
||||
}
|
||||
}
|
||||
|
||||
public static void record(
|
||||
Pose2d targetPose, Pose2d pose, double voltage,
|
||||
List<Integer> lastDriveEncPositions, List<Integer> lastDriveEncVels, List<Integer> lastTrackingEncPositions, List<Integer> lastTrackingEncVels
|
||||
) {
|
||||
long nsTime = System.nanoTime();
|
||||
if (nsTime - log.nsStart > 3 * 60 * 1_000_000_000L) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.nsTimes.add(nsTime);
|
||||
|
||||
log.targetXs.add(targetPose.getX());
|
||||
log.targetYs.add(targetPose.getY());
|
||||
log.targetHeadings.add(targetPose.getHeading());
|
||||
|
||||
log.xs.add(pose.getX());
|
||||
log.ys.add(pose.getY());
|
||||
log.headings.add(pose.getHeading());
|
||||
|
||||
log.voltages.add(voltage);
|
||||
|
||||
while (log.driveEncPositions.size() < lastDriveEncPositions.size()) {
|
||||
log.driveEncPositions.add(new ArrayList<>());
|
||||
}
|
||||
while (log.driveEncVels.size() < lastDriveEncVels.size()) {
|
||||
log.driveEncVels.add(new ArrayList<>());
|
||||
}
|
||||
while (log.trackingEncPositions.size() < lastTrackingEncPositions.size()) {
|
||||
log.trackingEncPositions.add(new ArrayList<>());
|
||||
}
|
||||
while (log.trackingEncVels.size() < lastTrackingEncVels.size()) {
|
||||
log.trackingEncVels.add(new ArrayList<>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastDriveEncPositions.size(); i++) {
|
||||
log.driveEncPositions.get(i).add(lastDriveEncPositions.get(i));
|
||||
}
|
||||
for (int i = 0; i < lastDriveEncVels.size(); i++) {
|
||||
log.driveEncVels.get(i).add(lastDriveEncVels.get(i));
|
||||
}
|
||||
for (int i = 0; i < lastTrackingEncPositions.size(); i++) {
|
||||
log.trackingEncPositions.get(i).add(lastTrackingEncPositions.get(i));
|
||||
}
|
||||
for (int i = 0; i < lastTrackingEncVels.size(); i++) {
|
||||
log.trackingEncVels.get(i).add(lastTrackingEncVels.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static final OpModeManagerNotifier.Notifications notifHandler = new OpModeManagerNotifier.Notifications() {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
final DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd__HH_mm_ss_SSS");
|
||||
|
||||
final ObjectWriter jsonWriter = new ObjectMapper(new JsonFactory())
|
||||
.writerWithDefaultPrettyPrinter();
|
||||
|
||||
@Override
|
||||
public void onOpModePreInit(OpMode opMode) {
|
||||
log = new LogFile(opMode.getClass().getCanonicalName());
|
||||
|
||||
// clean up old files
|
||||
File[] fs = Objects.requireNonNull(ROOT.listFiles());
|
||||
Arrays.sort(fs, (a, b) -> Long.compare(a.lastModified(), b.lastModified()));
|
||||
long totalSizeBytes = 0;
|
||||
for (File f : fs) {
|
||||
totalSizeBytes += f.length();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < fs.length && totalSizeBytes >= 32 * 1000 * 1000) {
|
||||
totalSizeBytes -= fs[i].length();
|
||||
if (!fs[i].delete()) {
|
||||
RobotLog.setGlobalErrorMsg("Unable to delete file " + fs[i].getAbsolutePath());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpModePreStart(OpMode opMode) {
|
||||
log.nsStart = System.nanoTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpModePostStop(OpMode opMode) {
|
||||
log.nsStop = System.nanoTime();
|
||||
|
||||
if (!(opMode instanceof OpModeManagerImpl.DefaultOpMode)) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
ROOT.mkdirs();
|
||||
|
||||
String filename = dateFormat.format(new Date(log.msInit)) + "__" + opMode.getClass().getSimpleName() + ".json";
|
||||
File file = new File(ROOT, filename);
|
||||
try {
|
||||
jsonWriter.writeValue(file, log);
|
||||
} catch (IOException e) {
|
||||
RobotLog.setGlobalErrorMsg(new RuntimeException(e),
|
||||
"Unable to write data to " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@WebHandlerRegistrar
|
||||
public static void registerRoutes(Context context, WebHandlerManager manager) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
ROOT.mkdirs();
|
||||
|
||||
// op mode manager only stores a weak reference, so we need to keep notifHandler alive ourselves
|
||||
// don't use @OnCreateEventLoop because it's unreliable
|
||||
OpModeManagerImpl.getOpModeManagerOfActivity(
|
||||
AppUtil.getInstance().getActivity()
|
||||
).registerListener(notifHandler);
|
||||
|
||||
manager.register("/logs", session -> {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("<!doctype html><html><head><title>Logs</title></head><body><ul>");
|
||||
File[] fs = Objects.requireNonNull(ROOT.listFiles());
|
||||
Arrays.sort(fs, (a, b) -> Long.compare(b.lastModified(), a.lastModified()));
|
||||
for (File f : fs) {
|
||||
sb.append("<li><a href=\"/logs/download?file=");
|
||||
sb.append(f.getName());
|
||||
sb.append("\" download=\"");
|
||||
sb.append(f.getName());
|
||||
sb.append("\">");
|
||||
sb.append(f.getName());
|
||||
sb.append("</a></li>");
|
||||
}
|
||||
sb.append("</ul></body></html>");
|
||||
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK,
|
||||
NanoHTTPD.MIME_HTML, sb.toString());
|
||||
});
|
||||
|
||||
manager.register("/logs/download", session -> {
|
||||
final String[] pairs = session.getQueryParameterString().split("&");
|
||||
if (pairs.length != 1) {
|
||||
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
NanoHTTPD.MIME_PLAINTEXT, "expected one query parameter, got " + pairs.length);
|
||||
}
|
||||
|
||||
final String[] parts = pairs[0].split("=");
|
||||
if (!parts[0].equals("file")) {
|
||||
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
NanoHTTPD.MIME_PLAINTEXT, "expected file query parameter, got " + parts[0]);
|
||||
}
|
||||
|
||||
File f = new File(ROOT, parts[1]);
|
||||
if (!f.exists()) {
|
||||
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
NanoHTTPD.MIME_PLAINTEXT, "file " + f + " doesn't exist");
|
||||
}
|
||||
|
||||
return NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK,
|
||||
"application/json", new FileInputStream(f));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.internal.system.AppUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility functions for log files.
|
||||
*/
|
||||
public class LoggingUtil {
|
||||
public static final File ROAD_RUNNER_FOLDER =
|
||||
new File(AppUtil.ROOT_FOLDER + "/RoadRunner/");
|
||||
|
||||
private static final long LOG_QUOTA = 25 * 1024 * 1024; // 25MB log quota for now
|
||||
|
||||
private static void buildLogList(List<File> logFiles, File dir) {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
buildLogList(logFiles, file);
|
||||
} else {
|
||||
logFiles.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void pruneLogsIfNecessary() {
|
||||
List<File> logFiles = new ArrayList<>();
|
||||
buildLogList(logFiles, ROAD_RUNNER_FOLDER);
|
||||
Collections.sort(logFiles, (lhs, rhs) ->
|
||||
Long.compare(lhs.lastModified(), rhs.lastModified()));
|
||||
|
||||
long dirSize = 0;
|
||||
for (File file: logFiles) {
|
||||
dirSize += file.length();
|
||||
}
|
||||
|
||||
while (dirSize > LOG_QUOTA) {
|
||||
if (logFiles.size() == 0) break;
|
||||
File fileToRemove = logFiles.remove(0);
|
||||
dirSize -= fileToRemove.length();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
fileToRemove.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a log file with the provided name
|
||||
*/
|
||||
public static File getLogFile(String name) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
ROAD_RUNNER_FOLDER.mkdirs();
|
||||
|
||||
pruneLogsIfNecessary();
|
||||
|
||||
return new File(ROAD_RUNNER_FOLDER, name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import com.qualcomm.hardware.lynx.LynxModule;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.internal.system.Misc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Collection of utilites for interacting with Lynx modules.
|
||||
*/
|
||||
public class LynxModuleUtil {
|
||||
|
||||
private static final LynxFirmwareVersion MIN_VERSION = new LynxFirmwareVersion(1, 8, 2);
|
||||
|
||||
/**
|
||||
* Parsed representation of a Lynx module firmware version.
|
||||
*/
|
||||
public static class LynxFirmwareVersion implements Comparable<LynxFirmwareVersion> {
|
||||
public final int major;
|
||||
public final int minor;
|
||||
public final int eng;
|
||||
|
||||
public LynxFirmwareVersion(int major, int minor, int eng) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.eng = eng;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof LynxFirmwareVersion) {
|
||||
LynxFirmwareVersion otherVersion = (LynxFirmwareVersion) other;
|
||||
return major == otherVersion.major && minor == otherVersion.minor &&
|
||||
eng == otherVersion.eng;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LynxFirmwareVersion other) {
|
||||
int majorComp = Integer.compare(major, other.major);
|
||||
if (majorComp == 0) {
|
||||
int minorComp = Integer.compare(minor, other.minor);
|
||||
if (minorComp == 0) {
|
||||
return Integer.compare(eng, other.eng);
|
||||
} else {
|
||||
return minorComp;
|
||||
}
|
||||
} else {
|
||||
return majorComp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Misc.formatInvariant("%d.%d.%d", major, minor, eng);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and parse Lynx module firmware version.
|
||||
* @param module Lynx module
|
||||
* @return parsed firmware version
|
||||
*/
|
||||
public static LynxFirmwareVersion getFirmwareVersion(LynxModule module) {
|
||||
String versionString = module.getNullableFirmwareVersionString();
|
||||
if (versionString == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = versionString.split("[ :,]+");
|
||||
try {
|
||||
// note: for now, we ignore the hardware entry
|
||||
return new LynxFirmwareVersion(
|
||||
Integer.parseInt(parts[3]),
|
||||
Integer.parseInt(parts[5]),
|
||||
Integer.parseInt(parts[7])
|
||||
);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception indicating an outdated Lynx firmware version.
|
||||
*/
|
||||
public static class LynxFirmwareVersionException extends RuntimeException {
|
||||
public LynxFirmwareVersionException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure all of the Lynx modules attached to the robot satisfy the minimum requirement.
|
||||
* @param hardwareMap hardware map containing Lynx modules
|
||||
*/
|
||||
public static void ensureMinimumFirmwareVersion(HardwareMap hardwareMap) {
|
||||
Map<String, LynxFirmwareVersion> outdatedModules = new HashMap<>();
|
||||
for (LynxModule module : hardwareMap.getAll(LynxModule.class)) {
|
||||
LynxFirmwareVersion version = getFirmwareVersion(module);
|
||||
if (version == null || version.compareTo(MIN_VERSION) < 0) {
|
||||
for (String name : hardwareMap.getNamesOf(module)) {
|
||||
outdatedModules.put(name, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (outdatedModules.size() > 0) {
|
||||
StringBuilder msgBuilder = new StringBuilder();
|
||||
msgBuilder.append("One or more of the attached Lynx modules has outdated firmware\n");
|
||||
msgBuilder.append(Misc.formatInvariant("Mandatory minimum firmware version for Road Runner: %s\n",
|
||||
MIN_VERSION.toString()));
|
||||
for (Map.Entry<String, LynxFirmwareVersion> entry : outdatedModules.entrySet()) {
|
||||
msgBuilder.append(Misc.formatInvariant(
|
||||
"\t%s: %s\n", entry.getKey(),
|
||||
entry.getValue() == null ? "Unknown" : entry.getValue().toString()));
|
||||
}
|
||||
throw new LynxFirmwareVersionException(msgBuilder.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
/**
|
||||
* This is a PID controller (https://en.wikipedia.org/wiki/PID_controller)
|
||||
* for your robot. Internally, it performs all the calculations for you.
|
||||
* You need to tune your values to the appropriate amounts in order
|
||||
* to properly utilize these calculations.
|
||||
* <p>
|
||||
* The equation we will use is:
|
||||
* u(t) = kP * e(t) + kI * int(0,t)[e(t')dt'] + kD * e'(t) + kF
|
||||
* where e(t) = r(t) - y(t) and r(t) is the setpoint and y(t) is the
|
||||
* measured value. If we consider e(t) the positional error, then
|
||||
* int(0,t)[e(t')dt'] is the total error and e'(t) is the velocity error.
|
||||
*/
|
||||
public class PersonalPID {
|
||||
|
||||
private double kP, kI, kD, kF;
|
||||
private double setPoint;
|
||||
private double measuredValue;
|
||||
private double minIntegral, maxIntegral;
|
||||
|
||||
private double errorVal_p;
|
||||
private double errorVal_v;
|
||||
|
||||
private double totalError;
|
||||
private double prevErrorVal;
|
||||
|
||||
private double errorTolerance_p = 0.05;
|
||||
private double errorTolerance_v = Double.POSITIVE_INFINITY;
|
||||
|
||||
private double lastTimeStamp;
|
||||
private double period;
|
||||
|
||||
/**
|
||||
* The base constructor for the PIDF controller
|
||||
*/
|
||||
public PersonalPID(double kp, double ki, double kd, double kf) {
|
||||
this(kp, ki, kd, kf, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the full constructor for the PIDF controller. Our PIDF controller
|
||||
* includes a feed-forward value which is useful for fighting friction and gravity.
|
||||
* Our errorVal represents the return of e(t) and prevErrorVal is the previous error.
|
||||
*
|
||||
* @param sp The setpoint of the pid control loop.
|
||||
* @param pv The measured value of he pid control loop. We want sp = pv, or to the degree
|
||||
* such that sp - pv, or e(t) < tolerance.
|
||||
*/
|
||||
public PersonalPID(double kp, double ki, double kd, double kf, double sp, double pv) {
|
||||
kP = kp;
|
||||
kI = ki;
|
||||
kD = kd;
|
||||
kF = kf;
|
||||
|
||||
setPoint = sp;
|
||||
measuredValue = pv;
|
||||
|
||||
minIntegral = -1.0;
|
||||
maxIntegral = 1.0;
|
||||
|
||||
lastTimeStamp = 0;
|
||||
period = 0;
|
||||
|
||||
errorVal_p = setPoint - measuredValue;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
totalError = 0;
|
||||
prevErrorVal = 0;
|
||||
lastTimeStamp = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error which is considered tolerable for use with {@link #atSetPoint()}.
|
||||
*
|
||||
* @param positionTolerance Position error which is tolerable.
|
||||
*/
|
||||
public void setTolerance(double positionTolerance) {
|
||||
setTolerance(positionTolerance, Double.POSITIVE_INFINITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the error which is considered tolerable for use with {@link #atSetPoint()}.
|
||||
*
|
||||
* @param positionTolerance Position error which is tolerable.
|
||||
* @param velocityTolerance Velocity error which is tolerable.
|
||||
*/
|
||||
public void setTolerance(double positionTolerance, double velocityTolerance) {
|
||||
errorTolerance_p = positionTolerance;
|
||||
errorTolerance_v = velocityTolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current setpoint of the PIDFController.
|
||||
*
|
||||
* @return The current setpoint.
|
||||
*/
|
||||
public double getSetPoint() {
|
||||
return setPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setpoint for the PIDFController
|
||||
*
|
||||
* @param sp The desired setpoint.
|
||||
*/
|
||||
public void setSetPoint(double sp) {
|
||||
setPoint = sp;
|
||||
errorVal_p = setPoint - measuredValue;
|
||||
errorVal_v = (errorVal_p - prevErrorVal) / period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the error is within the percentage of the total input range, determined by
|
||||
* {@link #setTolerance}.
|
||||
*
|
||||
* @return Whether the error is within the acceptable bounds.
|
||||
*/
|
||||
public boolean atSetPoint() {
|
||||
return Math.abs(errorVal_p) < errorTolerance_p
|
||||
&& Math.abs(errorVal_v) < errorTolerance_v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the PIDF coefficients
|
||||
*/
|
||||
public double[] getCoefficients() {
|
||||
return new double[]{kP, kI, kD, kF};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the positional error e(t)
|
||||
*/
|
||||
public double getPositionError() {
|
||||
return errorVal_p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tolerances of the controller
|
||||
*/
|
||||
public double[] getTolerance() {
|
||||
return new double[]{errorTolerance_p, errorTolerance_v};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the velocity error e'(t)
|
||||
*/
|
||||
public double getVelocityError() {
|
||||
return errorVal_v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next output of the PIDF controller.
|
||||
*
|
||||
* @return the next output using the current measured value via
|
||||
* {@link #calculate(double)}.
|
||||
*/
|
||||
public double calculate() {
|
||||
return calculate(measuredValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next output of the PIDF controller.
|
||||
*
|
||||
* @param pv The given measured value.
|
||||
* @param sp The given setpoint.
|
||||
* @return the next output using the given measurd value via
|
||||
* {@link #calculate(double)}.
|
||||
*/
|
||||
public double calculate(double pv, double sp) {
|
||||
// set the setpoint to the provided value
|
||||
setSetPoint(sp);
|
||||
return calculate(pv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the control value, u(t).
|
||||
*
|
||||
* @param pv The current measurement of the process variable.
|
||||
* @return the value produced by u(t).
|
||||
*/
|
||||
public double calculate(double pv) {
|
||||
prevErrorVal = errorVal_p;
|
||||
|
||||
double currentTimeStamp = (double) System.nanoTime() / 1E9;
|
||||
if (lastTimeStamp == 0) lastTimeStamp = currentTimeStamp;
|
||||
period = currentTimeStamp - lastTimeStamp;
|
||||
lastTimeStamp = currentTimeStamp;
|
||||
|
||||
if (measuredValue == pv) {
|
||||
errorVal_p = setPoint - measuredValue;
|
||||
} else {
|
||||
errorVal_p = setPoint - pv;
|
||||
measuredValue = pv;
|
||||
}
|
||||
|
||||
if (Math.abs(period) > 1E-6) {
|
||||
errorVal_v = (errorVal_p - prevErrorVal) / period;
|
||||
} else {
|
||||
errorVal_v = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
if total error is the integral from 0 to t of e(t')dt', and
|
||||
e(t) = sp - pv, then the total error, E(t), equals sp*t - pv*t.
|
||||
*/
|
||||
totalError += period * (setPoint - measuredValue);
|
||||
totalError = totalError < minIntegral ? minIntegral : Math.min(maxIntegral, totalError);
|
||||
|
||||
// returns u(t)
|
||||
return kP * errorVal_p + kI * totalError + kD * errorVal_v + kF * setPoint;
|
||||
}
|
||||
|
||||
public void setPIDF(double kp, double ki, double kd, double kf) {
|
||||
kP = kp;
|
||||
kI = ki;
|
||||
kD = kd;
|
||||
kF = kf;
|
||||
}
|
||||
|
||||
public void setIntegrationBounds(double integralMin, double integralMax) {
|
||||
minIntegral = integralMin;
|
||||
maxIntegral = integralMax;
|
||||
}
|
||||
|
||||
public void clearTotalError() {
|
||||
totalError = 0;
|
||||
}
|
||||
|
||||
public void setP(double kp) {
|
||||
kP = kp;
|
||||
}
|
||||
|
||||
public void setI(double ki) {
|
||||
kI = ki;
|
||||
}
|
||||
|
||||
public void setD(double kd) {
|
||||
kD = kd;
|
||||
}
|
||||
|
||||
public void setF(double kf) {
|
||||
kF = kf;
|
||||
}
|
||||
|
||||
public double getP() {
|
||||
return kP;
|
||||
}
|
||||
|
||||
public double getI() {
|
||||
return kI;
|
||||
}
|
||||
|
||||
public double getD() {
|
||||
return kD;
|
||||
}
|
||||
|
||||
public double getF() {
|
||||
return kF;
|
||||
}
|
||||
|
||||
public double getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package org.firstinspires.ftc.teamcode.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.acmerobotics.roadrunner.kinematics.Kinematics;
|
||||
|
||||
import org.apache.commons.math3.stat.regression.SimpleRegression;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Various regression utilities.
|
||||
*/
|
||||
public class RegressionUtil {
|
||||
|
||||
/**
|
||||
* Feedforward parameter estimates from the ramp regression and additional summary statistics
|
||||
*/
|
||||
public static class RampResult {
|
||||
public final double kV, kStatic, rSquare;
|
||||
|
||||
public RampResult(double kV, double kStatic, double rSquare) {
|
||||
this.kV = kV;
|
||||
this.kStatic = kStatic;
|
||||
this.rSquare = rSquare;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feedforward parameter estimates from the ramp regression and additional summary statistics
|
||||
*/
|
||||
public static class AccelResult {
|
||||
public final double kA, rSquare;
|
||||
|
||||
public AccelResult(double kA, double rSquare) {
|
||||
this.kA = kA;
|
||||
this.rSquare = rSquare;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Numerically compute dy/dx from the given x and y values. The returned list is padded to match
|
||||
* the length of the original sequences.
|
||||
*
|
||||
* @param x x-values
|
||||
* @param y y-values
|
||||
* @return derivative values
|
||||
*/
|
||||
private static List<Double> numericalDerivative(List<Double> x, List<Double> y) {
|
||||
List<Double> deriv = new ArrayList<>(x.size());
|
||||
for (int i = 1; i < x.size() - 1; i++) {
|
||||
deriv.add(
|
||||
(y.get(i + 1) - y.get(i - 1)) /
|
||||
(x.get(i + 1) - x.get(i - 1))
|
||||
);
|
||||
}
|
||||
// copy endpoints to pad output
|
||||
deriv.add(0, deriv.get(0));
|
||||
deriv.add(deriv.get(deriv.size() - 1));
|
||||
return deriv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run regression to compute velocity and static feedforward from ramp test data.
|
||||
*
|
||||
* Here's the general procedure for gathering the requisite data:
|
||||
* 1. Slowly ramp the motor power/voltage and record encoder values along the way.
|
||||
* 2. Run a linear regression on the encoder velocity vs. motor power plot to obtain a slope
|
||||
* (kV) and an optional intercept (kStatic).
|
||||
*
|
||||
* @param timeSamples time samples
|
||||
* @param positionSamples position samples
|
||||
* @param powerSamples power samples
|
||||
* @param fitStatic fit kStatic
|
||||
* @param file log file
|
||||
*/
|
||||
public static RampResult fitRampData(List<Double> timeSamples, List<Double> positionSamples,
|
||||
List<Double> powerSamples, boolean fitStatic,
|
||||
@Nullable File file) {
|
||||
if (file != null) {
|
||||
try (PrintWriter pw = new PrintWriter(file)) {
|
||||
pw.println("time,position,power");
|
||||
for (int i = 0; i < timeSamples.size(); i++) {
|
||||
double time = timeSamples.get(i);
|
||||
double pos = positionSamples.get(i);
|
||||
double power = powerSamples.get(i);
|
||||
pw.println(time + "," + pos + "," + power);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
List<Double> velSamples = numericalDerivative(timeSamples, positionSamples);
|
||||
|
||||
SimpleRegression rampReg = new SimpleRegression(fitStatic);
|
||||
for (int i = 0; i < timeSamples.size(); i++) {
|
||||
double vel = velSamples.get(i);
|
||||
double power = powerSamples.get(i);
|
||||
|
||||
rampReg.addData(vel, power);
|
||||
}
|
||||
|
||||
return new RampResult(Math.abs(rampReg.getSlope()), Math.abs(rampReg.getIntercept()),
|
||||
rampReg.getRSquare());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run regression to compute acceleration feedforward.
|
||||
*
|
||||
* @param timeSamples time samples
|
||||
* @param positionSamples position samples
|
||||
* @param powerSamples power samples
|
||||
* @param rampResult ramp result
|
||||
* @param file log file
|
||||
*/
|
||||
public static AccelResult fitAccelData(List<Double> timeSamples, List<Double> positionSamples,
|
||||
List<Double> powerSamples, RampResult rampResult,
|
||||
@Nullable File file) {
|
||||
if (file != null) {
|
||||
try (PrintWriter pw = new PrintWriter(file)) {
|
||||
pw.println("time,position,power");
|
||||
for (int i = 0; i < timeSamples.size(); i++) {
|
||||
double time = timeSamples.get(i);
|
||||
double pos = positionSamples.get(i);
|
||||
double power = powerSamples.get(i);
|
||||
pw.println(time + "," + pos + "," + power);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
List<Double> velSamples = numericalDerivative(timeSamples, positionSamples);
|
||||
List<Double> accelSamples = numericalDerivative(timeSamples, velSamples);
|
||||
|
||||
SimpleRegression accelReg = new SimpleRegression(false);
|
||||
for (int i = 0; i < timeSamples.size(); i++) {
|
||||
double vel = velSamples.get(i);
|
||||
double accel = accelSamples.get(i);
|
||||
double power = powerSamples.get(i);
|
||||
|
||||
double powerFromVel = Kinematics.calculateMotorFeedforward(
|
||||
vel, 0.0, rampResult.kV, 0.0, rampResult.kStatic);
|
||||
double powerFromAccel = power - powerFromVel;
|
||||
|
||||
accelReg.addData(accel, powerFromAccel);
|
||||
}
|
||||
|
||||
return new AccelResult(Math.abs(accelReg.getSlope()), accelReg.getRSquare());
|
||||
}
|
||||
}
|
||||
1
TeamCode/src/main/res/raw/readme.md
Normal file
1
TeamCode/src/main/res/raw/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
Place your sound files in this folder to use them as project resources.
|
||||
4
TeamCode/src/main/res/values/strings.xml
Normal file
4
TeamCode/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
</resources>
|
||||
161
TeamCode/src/main/res/xml/teamwebcamcalibrations.xml
Normal file
161
TeamCode/src/main/res/xml/teamwebcamcalibrations.xml
Normal file
@@ -0,0 +1,161 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<!--
|
||||
This file can provide additional camera calibration settings beyond those built into the SDK itself.
|
||||
Each calibration is for a particular camera (indicated by USB vid & pid pair) and a particular
|
||||
capture resolution for the camera. Note: it is very important when capturing images used to calibrate
|
||||
a camera that the image acquisition tool can actually control this capture resolution within the camera
|
||||
itself and that you use this setting correctly. Many image acquistion tools do not in fact provide
|
||||
this level of control.
|
||||
|
||||
Beyond simply providing additional, new camera calibrations, calibrations provided herein can
|
||||
*replace/update* those that are builtin to the SDK. This matching is keyed, of course, by the
|
||||
(vid, pid, size) triple. Further, if such a calibration has the 'remove' attribute with value 'true',
|
||||
any existing calibration with that key is removed (and the calibration itself not added).
|
||||
|
||||
Calibrations are internally processed according to aspect ratio. If a format is requested in a size
|
||||
that is not calibrated, but a calibration does exist for the same aspect ratio on the same camera,
|
||||
then the latter will be scaled to accommodate the request. For example, if a 640x480 calibration
|
||||
is requested but only a 800x600 calibration exists for that camera, then the 800x600 is scaled
|
||||
down to service the 640x480 request.
|
||||
|
||||
Further, it is important to note that if *no* calibrations exist for a given camera, then Vuforia
|
||||
is offered the entire range of capture resolutions that the hardware can support (and it does its
|
||||
best to deal with the lack of calibration). However, if *any* calibrations are provided for a camera,
|
||||
then capture resolutions in those aspect ratios supported by the camera for which any calibrations
|
||||
are *not* provided are *not* offered. Thus, if you calibrate a camera but fail to calibrate all
|
||||
the camera's supported aspect ratios, you limit the choices of capture resolutions that Vuforia can
|
||||
select from.
|
||||
|
||||
One image acquisition program that supports control of camera capture resolution is YouCam 7:
|
||||
https://www.cyberlink.com/products/youcam/features_en_US.html
|
||||
|
||||
Programs that can process acquired images to determine camera calibration settings include:
|
||||
https://www.3dflow.net/3df-zephyr-free/ (see "Utilities/Images/Launch Camera Calibration" therein)
|
||||
http://graphics.cs.msu.ru/en/node/909
|
||||
Note that the type of images that must be acquired in order to calibrate is specific to the
|
||||
calibration software used.
|
||||
|
||||
The required contents are illustrated here by example. Note that for the attribute names, both the
|
||||
camelCase or the underscore_variations are supported; they are equivalent. The attributes for
|
||||
each Calibration are as follows:
|
||||
|
||||
size (int pair): space separated camera resolution (width, height).
|
||||
focalLength (float pair): space separated focal length value.
|
||||
principalPoint (float pair): space separated principal point values (width, height).
|
||||
distortionCoefficients (an 8-element float array): distortion coefficients in the following form
|
||||
(r:radial, t:tangential): [r0, r1, t0, t1, r2, r3, r4, r5]
|
||||
see https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html
|
||||
|
||||
The examples here are commented out as the values are built-in to the FTC SDK. They serve instead
|
||||
here as examples on how make your own.
|
||||
|
||||
-->
|
||||
<Calibrations>
|
||||
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- Microsoft Lifecam HD 3000 v1, Calibrated by PTC using unknown tooling -->
|
||||
<!-- <Camera vid="Microsoft" pid="0x0779">
|
||||
<Calibration
|
||||
size="640 480"
|
||||
focalLength="678.154f, 678.17f"
|
||||
principalPoint="318.135f, 228.374f"
|
||||
distortionCoefficients="0.154576f, -1.19143f, 0f, 0f, 2.06105f, 0f, 0f, 0f"
|
||||
/>
|
||||
</Camera> -->
|
||||
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- Microsoft Lifecam HD 3000 v2, Calibrated by PTC using unknown tooling -->
|
||||
<!-- <Camera vid="Microsoft" pid="0x0810">
|
||||
<Calibration
|
||||
size="640 480"
|
||||
focalLength="678.154f, 678.17f"
|
||||
principalPoint="318.135f, 228.374f"
|
||||
distortionCoefficients="0.154576f, -1.19143f, 0f, 0f, 2.06105f, 0f, 0f, 0f"
|
||||
/>
|
||||
</Camera> -->
|
||||
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- Logitech HD Webcam C310, Calibrated by by Robert Atkinson, 2018.05.30 using 3DF Zephyr -->
|
||||
<!-- <Camera vid="Logitech" pid="0x081B">
|
||||
<Calibration
|
||||
size="640 480"
|
||||
focalLength="821.993f, 821.993f"
|
||||
principalPoint="330.489f, 248.997f"
|
||||
distortionCoefficients="-0.018522, 1.03979, 0, 0, -3.3171, 0, 0, 0"
|
||||
/>
|
||||
|
||||
<Calibration
|
||||
size="640 360"
|
||||
focalLength="715.307f, 715.307f"
|
||||
principalPoint="319.759f, 188.917f"
|
||||
distortionCoefficients="-0.0258948, 1.06258, 0, 0, -3.40245, 0, 0, 0"
|
||||
/>
|
||||
</Camera> -->
|
||||
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- Logitech HD Pro Webcam C920, Calibrated by Robert Atkinson, 2018.05.30 using 3DF Zephyr -->
|
||||
<!-- <Camera vid="Logitech" pid="0x082D">
|
||||
|
||||
<Calibration
|
||||
size="640 480"
|
||||
focalLength="622.001f, 622.001f"
|
||||
principalPoint="319.803f, 241.251f"
|
||||
distortionCoefficients="0.1208, -0.261599, 0, 0, 0.10308, 0, 0, 0"
|
||||
/>
|
||||
|
||||
<Calibration
|
||||
size="800 600"
|
||||
focalLength="775.79f, 775.79f"
|
||||
principalPoint="400.898f, 300.79f"
|
||||
distortionCoefficients="0.112507, -0.272067, 0, 0, 0.15775, 0, 0, 0"
|
||||
/>
|
||||
|
||||
<Calibration
|
||||
size="640 360"
|
||||
focalLength="463.566f, 463.566f"
|
||||
principalPoint="316.402f, 176.412f"
|
||||
distortionCoefficients="0.111626 , -0.255626, 0, 0, 0.107992, 0, 0, 0"
|
||||
/>
|
||||
|
||||
<Calibration
|
||||
size="1920, 1080"
|
||||
focalLength="1385.92f , 1385.92f"
|
||||
principalPoint="951.982f , 534.084f"
|
||||
distortionCoefficients="0.117627, -0.248549, 0, 0, 0.107441, 0, 0, 0"
|
||||
/>
|
||||
|
||||
<Calibration
|
||||
size="800, 448"
|
||||
focalLength="578.272f , 578.272f"
|
||||
principalPoint="402.145f , 221.506f"
|
||||
distortionCoefficients="0.12175, -0.251652 , 0, 0, 0.112142, 0, 0, 0"
|
||||
/>
|
||||
|
||||
<Calibration
|
||||
size="864, 480"
|
||||
focalLength="626.909f , 626.909f"
|
||||
principalPoint="426.007f , 236.834f"
|
||||
distortionCoefficients="0.120988, -0.253336 , 0, 0, 0.102445, 0, 0, 0"
|
||||
/>
|
||||
|
||||
</Camera> -->
|
||||
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- Logitech HD Webcam C270, Calibrated by Noah Andrews, 2019.03.13 using 3DF Zephyr -->
|
||||
<!--<Camera vid="Logitech" pid="0x0825">
|
||||
<Calibration
|
||||
size="640 480"
|
||||
focalLength="822.317f, 822.317f"
|
||||
principalPoint="319.495f, 242.502f"
|
||||
distortionCoefficients="-0.0449369, 1.17277, 0, 0, -3.63244, 0, 0, 0"
|
||||
/>
|
||||
</Camera> -->
|
||||
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
</Calibrations>
|
||||
Reference in New Issue
Block a user