Compare commits
111 Commits
f326c0d033
...
branch-ped
Author | SHA1 | Date | |
---|---|---|---|
156423422c | |||
e534f46efb | |||
fce14b3753 | |||
4ed1c8c615 | |||
2594ff79ca | |||
fa49b48441 | |||
d9d0f11de7 | |||
4dc7b493fa | |||
a149909e82 | |||
dbe9c76ee6 | |||
d41f368fe8 | |||
51c3e0bd93 | |||
ac1be6387b | |||
315e3b4240 | |||
fb570d6be8 | |||
a0126ba1a2 | |||
fb7f160a9f | |||
af672c7807 | |||
d7f145300f | |||
6ce588fc79 | |||
48019d026b | |||
416ba0d1ad | |||
6b35895532 | |||
03d95d791e | |||
9d2cd48287 | |||
ecc49aa778 | |||
1da45a36c6 | |||
c303962469 | |||
8e84495f1a | |||
01d55d76f4 | |||
c6ef075c48 | |||
5cf1596910 | |||
7381cb6b23 | |||
a01ac85ef7 | |||
a890105110 | |||
ce080b0b51 | |||
e4cff7367d | |||
263100de3b | |||
02d8ea7615 | |||
01453c24d4 | |||
d586fc3e74 | |||
416cab6f51 | |||
7ad781b8ac | |||
79d6297a46 | |||
0e66ff3715 | |||
ddfb994c78 | |||
df17f93fd3 | |||
a6ed85a5c4 | |||
d955adf47c | |||
071e405f92 | |||
e69e475565 | |||
e291d590a6 | |||
a6b4dbebc4 | |||
42246e0296 | |||
57db0dc66b | |||
1b1197db0c | |||
2584ce8d45 | |||
6115d0311f | |||
fc9b93aab4 | |||
4ff75136aa | |||
dd6d43d9c0 | |||
219e07813d | |||
7cce1c43a9 | |||
629c103d77 | |||
973ca84a46 | |||
5f2fcaf51f | |||
334252c421 | |||
3c2d54523a | |||
2f2c6e6295 | |||
dc1b43e13f | |||
b12b121447 | |||
5cd5bdd04e | |||
a4747329ef | |||
76e5f3e4b9 | |||
1486131931 | |||
7c43265fd6 | |||
5c0fd0b519 | |||
e002d64689 | |||
9fa28159da | |||
3fc724ee39 | |||
9db94c089d | |||
0c14630d40 | |||
1ae2be3dcf | |||
491bb42123 | |||
5fbb278d09 | |||
1c857f2f95 | |||
db869f3e01 | |||
332704b536 | |||
47151cbebd | |||
98cbe1e950 | |||
80ea431b29 | |||
9651735df3 | |||
b4af81739a | |||
77dbd7f5e2 | |||
b617f87bac | |||
8a88605057 | |||
970625f1c0 | |||
b38e9e6153 | |||
4fc5d6465b | |||
d0b06e2d59 | |||
2d9c57a373 | |||
45d77b8b35 | |||
4a3417ca49 | |||
1b7f3ca0d0 | |||
7c9fd6c18a | |||
b44d5e0ca5 | |||
6f6a0c9771 | |||
b7211333e6 | |||
57cc92bc47 | |||
05056c07b8 | |||
67b0165c32 |
33
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
body:
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: RR FTC Version
|
||||
description: Open `TeamCode/build.gradle` and read the version from the
|
||||
`implementation "com.acmerobotics.roadrunner:ftc:LIBRARY_VERSION_HERE"`
|
||||
line
|
||||
placeholder: 0.1.8
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Observed Behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: tuning
|
||||
attributes:
|
||||
label: Tuning Files
|
||||
description: Click the "Download" button on the tuning page and attach the file
|
||||
(if applicable)
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Robot Logs
|
||||
description: Download the relevant logs from `http://192.168.43.1:8080/logs` and
|
||||
attach them here
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="52"
|
||||
android:versionName="9.0.1">
|
||||
android:versionCode="55"
|
||||
android:versionName="10.0">
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
|
@ -99,7 +99,7 @@ public class BasicOmniOpMode_Linear extends LinearOpMode {
|
||||
rightFrontDrive.setDirection(DcMotor.Direction.FORWARD);
|
||||
rightBackDrive.setDirection(DcMotor.Direction.FORWARD);
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
telemetry.addData("Status", "Initialized");
|
||||
telemetry.update();
|
||||
|
||||
|
@ -83,14 +83,14 @@ public class BasicOpMode_Iterative extends OpMode
|
||||
}
|
||||
|
||||
/*
|
||||
* Code to run REPEATEDLY after the driver hits INIT, but before they hit PLAY
|
||||
* Code to run REPEATEDLY after the driver hits INIT, but before they hit START
|
||||
*/
|
||||
@Override
|
||||
public void init_loop() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Code to run ONCE when the driver hits PLAY
|
||||
* Code to run ONCE when the driver hits START
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
@ -98,7 +98,7 @@ public class BasicOpMode_Iterative extends OpMode
|
||||
}
|
||||
|
||||
/*
|
||||
* Code to run REPEATEDLY after the driver hits PLAY but before they hit STOP
|
||||
* Code to run REPEATEDLY after the driver hits START but before they hit STOP
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
|
@ -76,7 +76,7 @@ public class BasicOpMode_Linear extends LinearOpMode {
|
||||
leftDrive.setDirection(DcMotor.Direction.REVERSE);
|
||||
rightDrive.setDirection(DcMotor.Direction.FORWARD);
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
waitForStart();
|
||||
runtime.reset();
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import android.util.Size;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
@ -87,7 +88,7 @@ public class ConceptAprilTag extends LinearOpMode {
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
|
@ -84,7 +84,7 @@ public class ConceptAprilTagEasy extends LinearOpMode {
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
|
@ -0,0 +1,244 @@
|
||||
/* Copyright (c) 2024 Dryw Wade. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
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.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.AprilTagProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates the basics of AprilTag based localization.
|
||||
*
|
||||
* For an introduction to AprilTags, see the FTC-DOCS link below:
|
||||
* https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html
|
||||
*
|
||||
* In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default
|
||||
* "TagLibrary" will be used to compute the robot's location and orientation. This default TagLibrary contains
|
||||
* the current Season's AprilTags and a small set of "test Tags" in the high number range.
|
||||
*
|
||||
* When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the robot, relative to the field origin.
|
||||
* This information is provided in the "robotPose" member of the returned "detection".
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list.
|
||||
*/
|
||||
@TeleOp(name = "Concept: AprilTag Localization", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptAprilTagLocalization extends LinearOpMode {
|
||||
|
||||
private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera
|
||||
|
||||
/**
|
||||
* Variables to store the position and orientation of the camera on the robot. Setting these
|
||||
* values requires a definition of the axes of the camera and robot:
|
||||
*
|
||||
* Camera axes:
|
||||
* Origin location: Center of the lens
|
||||
* Axes orientation: +x right, +y down, +z forward (from camera's perspective)
|
||||
*
|
||||
* Robot axes (this is typical, but you can define this however you want):
|
||||
* Origin location: Center of the robot at field height
|
||||
* Axes orientation: +x right, +y forward, +z upward
|
||||
*
|
||||
* Position:
|
||||
* If all values are zero (no translation), that implies the camera is at the center of the
|
||||
* robot. Suppose your camera is positioned 5 inches to the left, 7 inches forward, and 12
|
||||
* inches above the ground - you would need to set the position to (-5, 7, 12).
|
||||
*
|
||||
* Orientation:
|
||||
* If all values are zero (no rotation), that implies the camera is pointing straight up. In
|
||||
* most cases, you'll need to set the pitch to -90 degrees (rotation about the x-axis), meaning
|
||||
* the camera is horizontal. Use a yaw of 0 if the camera is pointing forwards, +90 degrees if
|
||||
* it's pointing straight left, -90 degrees for straight right, etc. You can also set the roll
|
||||
* to +/-90 degrees if it's vertical, or 180 degrees if it's upside-down.
|
||||
*/
|
||||
private Position cameraPosition = new Position(DistanceUnit.INCH,
|
||||
0, 0, 0, 0);
|
||||
private YawPitchRollAngles cameraOrientation = new YawPitchRollAngles(AngleUnit.DEGREES,
|
||||
0, -90, 0, 0);
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the AprilTag processor.
|
||||
*/
|
||||
private AprilTagProcessor aprilTag;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the vision portal.
|
||||
*/
|
||||
private VisionPortal visionPortal;
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
initAprilTag();
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
while (opModeIsActive()) {
|
||||
|
||||
telemetryAprilTag();
|
||||
|
||||
// Push telemetry to the Driver Station.
|
||||
telemetry.update();
|
||||
|
||||
// Save CPU resources; can resume streaming when needed.
|
||||
if (gamepad1.dpad_down) {
|
||||
visionPortal.stopStreaming();
|
||||
} else if (gamepad1.dpad_up) {
|
||||
visionPortal.resumeStreaming();
|
||||
}
|
||||
|
||||
// Share the CPU.
|
||||
sleep(20);
|
||||
}
|
||||
|
||||
// Save more CPU resources when camera is no longer needed.
|
||||
visionPortal.close();
|
||||
|
||||
} // end method runOpMode()
|
||||
|
||||
/**
|
||||
* Initialize the AprilTag processor.
|
||||
*/
|
||||
private void initAprilTag() {
|
||||
|
||||
// Create the AprilTag processor.
|
||||
aprilTag = new AprilTagProcessor.Builder()
|
||||
|
||||
// The following default settings are available to un-comment and edit as needed.
|
||||
//.setDrawAxes(false)
|
||||
//.setDrawCubeProjection(false)
|
||||
//.setDrawTagOutline(true)
|
||||
//.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11)
|
||||
//.setTagLibrary(AprilTagGameDatabase.getCenterStageTagLibrary())
|
||||
//.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES)
|
||||
.setCameraPose(cameraPosition, cameraOrientation)
|
||||
|
||||
// == CAMERA CALIBRATION ==
|
||||
// If you do not manually specify calibration parameters, the SDK will attempt
|
||||
// to load a predefined calibration for your camera.
|
||||
//.setLensIntrinsics(578.272, 578.272, 402.145, 221.506)
|
||||
// ... these parameters are fx, fy, cx, cy.
|
||||
|
||||
.build();
|
||||
|
||||
// Adjust Image Decimation to trade-off detection-range for detection-rate.
|
||||
// eg: Some typical detection data using a Logitech C920 WebCam
|
||||
// Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second
|
||||
// Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second
|
||||
// Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second (default)
|
||||
// Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second (default)
|
||||
// Note: Decimation can be changed on-the-fly to adapt during a match.
|
||||
//aprilTag.setDecimation(3);
|
||||
|
||||
// Create the vision portal by using a builder.
|
||||
VisionPortal.Builder builder = new VisionPortal.Builder();
|
||||
|
||||
// Set the camera (webcam vs. built-in RC phone camera).
|
||||
if (USE_WEBCAM) {
|
||||
builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"));
|
||||
} else {
|
||||
builder.setCamera(BuiltinCameraDirection.BACK);
|
||||
}
|
||||
|
||||
// Choose a camera resolution. Not all cameras support all resolutions.
|
||||
//builder.setCameraResolution(new Size(640, 480));
|
||||
|
||||
// Enable the RC preview (LiveView). Set "false" to omit camera monitoring.
|
||||
//builder.enableLiveView(true);
|
||||
|
||||
// Set the stream format; MJPEG uses less bandwidth than default YUY2.
|
||||
//builder.setStreamFormat(VisionPortal.StreamFormat.YUY2);
|
||||
|
||||
// Choose whether or not LiveView stops if no processors are enabled.
|
||||
// If set "true", monitor shows solid orange screen if no processors enabled.
|
||||
// If set "false", monitor shows camera view without annotations.
|
||||
//builder.setAutoStopLiveView(false);
|
||||
|
||||
// Set and enable the processor.
|
||||
builder.addProcessor(aprilTag);
|
||||
|
||||
// Build the Vision Portal, using the above settings.
|
||||
visionPortal = builder.build();
|
||||
|
||||
// Disable or re-enable the aprilTag processor at any time.
|
||||
//visionPortal.setProcessorEnabled(aprilTag, true);
|
||||
|
||||
} // end method initAprilTag()
|
||||
|
||||
/**
|
||||
* Add telemetry about AprilTag detections.
|
||||
*/
|
||||
private void telemetryAprilTag() {
|
||||
|
||||
List<AprilTagDetection> currentDetections = aprilTag.getDetections();
|
||||
telemetry.addData("# AprilTags Detected", currentDetections.size());
|
||||
|
||||
// Step through the list of detections and display info for each one.
|
||||
for (AprilTagDetection detection : currentDetections) {
|
||||
if (detection.metadata != null) {
|
||||
telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name));
|
||||
telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)",
|
||||
detection.robotPose.getPosition().x,
|
||||
detection.robotPose.getPosition().y,
|
||||
detection.robotPose.getPosition().z));
|
||||
telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)",
|
||||
detection.robotPose.getOrientation().getPitch(AngleUnit.DEGREES),
|
||||
detection.robotPose.getOrientation().getRoll(AngleUnit.DEGREES),
|
||||
detection.robotPose.getOrientation().getYaw(AngleUnit.DEGREES)));
|
||||
} else {
|
||||
telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id));
|
||||
telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y));
|
||||
}
|
||||
} // end for() loop
|
||||
|
||||
// Add "key" information to telemetry
|
||||
telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist.");
|
||||
telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)");
|
||||
|
||||
} // end method telemetryAprilTag()
|
||||
|
||||
} // end class
|
@ -0,0 +1,104 @@
|
||||
/* Copyright (c) 2024 FIRST. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
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.hardware.camera.BuiltinCameraDirection;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
import org.firstinspires.ftc.vision.VisionPortal;
|
||||
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor;
|
||||
|
||||
/**
|
||||
* This OpMode demonstrates the basics of using multiple vision portals simultaneously
|
||||
*/
|
||||
@TeleOp(name = "Concept: AprilTagMultiPortal", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptAprilTagMultiPortal extends LinearOpMode
|
||||
{
|
||||
VisionPortal portal1;
|
||||
VisionPortal portal2;
|
||||
|
||||
AprilTagProcessor aprilTagProcessor1;
|
||||
AprilTagProcessor aprilTagProcessor2;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException
|
||||
{
|
||||
// Because we want to show two camera feeds simultaneously, we need to inform
|
||||
// the SDK that we want it to split the camera monitor area into two smaller
|
||||
// areas for us. It will then give us View IDs which we can pass to the individual
|
||||
// vision portals to allow them to properly hook into the UI in tandem.
|
||||
int[] viewIds = VisionPortal.makeMultiPortalView(2, VisionPortal.MultiPortalLayout.VERTICAL);
|
||||
|
||||
// We extract the two view IDs from the array to make our lives a little easier later.
|
||||
// NB: the array is 2 long because we asked for 2 portals up above.
|
||||
int portal1ViewId = viewIds[0];
|
||||
int portal2ViewId = viewIds[1];
|
||||
|
||||
// If we want to run AprilTag detection on two portals simultaneously,
|
||||
// we need to create two distinct instances of the AprilTag processor,
|
||||
// one for each portal. If you want to see more detail about different
|
||||
// options that you have when creating these processors, go check out
|
||||
// the ConceptAprilTag OpMode.
|
||||
aprilTagProcessor1 = AprilTagProcessor.easyCreateWithDefaults();
|
||||
aprilTagProcessor2 = AprilTagProcessor.easyCreateWithDefaults();
|
||||
|
||||
// Now we build both portals. The CRITICAL thing to notice here is the call to
|
||||
// setLiveViewContainerId(), where we pass in the IDs we received earlier from
|
||||
// makeMultiPortalView().
|
||||
portal1 = new VisionPortal.Builder()
|
||||
.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
|
||||
.setLiveViewContainerId(portal1ViewId)
|
||||
.addProcessor(aprilTagProcessor1)
|
||||
.build();
|
||||
portal2 = new VisionPortal.Builder()
|
||||
.setCamera(hardwareMap.get(WebcamName.class, "Webcam 2"))
|
||||
.setLiveViewContainerId(portal2ViewId)
|
||||
.addProcessor(aprilTagProcessor2)
|
||||
.build();
|
||||
|
||||
waitForStart();
|
||||
|
||||
// Main Loop
|
||||
while (opModeIsActive())
|
||||
{
|
||||
// Just show some basic telemetry to demonstrate both processors are working in parallel
|
||||
// on their respective cameras. If you want to see more detail about the information you
|
||||
// can get back from the processor, you should look at ConceptAprilTag.
|
||||
telemetry.addData("Number of tags in Camera 1", aprilTagProcessor1.getDetections().size());
|
||||
telemetry.addData("Number of tags in Camera 2", aprilTagProcessor2.getDetections().size());
|
||||
telemetry.update();
|
||||
sleep(20);
|
||||
}
|
||||
}
|
||||
}
|
@ -98,7 +98,7 @@ public class ConceptAprilTagOptimizeExposure extends LinearOpMode
|
||||
|
||||
// Wait for the match to begin.
|
||||
telemetry.addData("Camera preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class ConceptAprilTagSwitchableCameras extends LinearOpMode {
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
|
@ -1,202 +0,0 @@
|
||||
/* Copyright (c) 2023 FIRST. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
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.hardware.camera.BuiltinCameraDirection;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
import org.firstinspires.ftc.robotcore.external.tfod.Recognition;
|
||||
import org.firstinspires.ftc.vision.VisionPortal;
|
||||
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection;
|
||||
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor;
|
||||
import org.firstinspires.ftc.vision.tfod.TfodProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates the basics of using both AprilTag recognition and TensorFlow
|
||||
* Object Detection.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list.
|
||||
*/
|
||||
@TeleOp(name = "Concept: Double Vision", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptDoubleVision extends LinearOpMode {
|
||||
private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the AprilTag processor.
|
||||
*/
|
||||
private AprilTagProcessor aprilTag;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private TfodProcessor tfod;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the vision portal.
|
||||
*/
|
||||
private VisionPortal myVisionPortal;
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
initDoubleVision();
|
||||
|
||||
// This OpMode loops continuously, allowing the user to switch between
|
||||
// AprilTag and TensorFlow Object Detection (TFOD) image processors.
|
||||
while (!isStopRequested()) {
|
||||
|
||||
if (opModeInInit()) {
|
||||
telemetry.addData("DS preview on/off","3 dots, Camera Stream");
|
||||
telemetry.addLine();
|
||||
telemetry.addLine("----------------------------------------");
|
||||
}
|
||||
|
||||
if (myVisionPortal.getProcessorEnabled(aprilTag)) {
|
||||
// User instructions: Dpad left or Dpad right.
|
||||
telemetry.addLine("Dpad Left to disable AprilTag");
|
||||
telemetry.addLine();
|
||||
telemetryAprilTag();
|
||||
} else {
|
||||
telemetry.addLine("Dpad Right to enable AprilTag");
|
||||
}
|
||||
telemetry.addLine();
|
||||
telemetry.addLine("----------------------------------------");
|
||||
if (myVisionPortal.getProcessorEnabled(tfod)) {
|
||||
telemetry.addLine("Dpad Down to disable TFOD");
|
||||
telemetry.addLine();
|
||||
telemetryTfod();
|
||||
} else {
|
||||
telemetry.addLine("Dpad Up to enable TFOD");
|
||||
}
|
||||
|
||||
// Push telemetry to the Driver Station.
|
||||
telemetry.update();
|
||||
|
||||
if (gamepad1.dpad_left) {
|
||||
myVisionPortal.setProcessorEnabled(aprilTag, false);
|
||||
} else if (gamepad1.dpad_right) {
|
||||
myVisionPortal.setProcessorEnabled(aprilTag, true);
|
||||
}
|
||||
if (gamepad1.dpad_down) {
|
||||
myVisionPortal.setProcessorEnabled(tfod, false);
|
||||
} else if (gamepad1.dpad_up) {
|
||||
myVisionPortal.setProcessorEnabled(tfod, true);
|
||||
}
|
||||
|
||||
sleep(20);
|
||||
|
||||
} // end while loop
|
||||
|
||||
} // end method runOpMode()
|
||||
|
||||
|
||||
/**
|
||||
* Initialize AprilTag and TFOD.
|
||||
*/
|
||||
private void initDoubleVision() {
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// AprilTag Configuration
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
aprilTag = new AprilTagProcessor.Builder()
|
||||
.build();
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// TFOD Configuration
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
tfod = new TfodProcessor.Builder()
|
||||
.build();
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Camera Configuration
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
if (USE_WEBCAM) {
|
||||
myVisionPortal = new VisionPortal.Builder()
|
||||
.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
|
||||
.addProcessors(tfod, aprilTag)
|
||||
.build();
|
||||
} else {
|
||||
myVisionPortal = new VisionPortal.Builder()
|
||||
.setCamera(BuiltinCameraDirection.BACK)
|
||||
.addProcessors(tfod, aprilTag)
|
||||
.build();
|
||||
}
|
||||
} // end initDoubleVision()
|
||||
|
||||
/**
|
||||
* Add telemetry about AprilTag detections.
|
||||
*/
|
||||
private void telemetryAprilTag() {
|
||||
List<AprilTagDetection> currentDetections = aprilTag.getDetections();
|
||||
telemetry.addData("# AprilTags Detected", currentDetections.size());
|
||||
|
||||
// Step through the list of detections and display info for each one.
|
||||
for (AprilTagDetection detection : currentDetections) {
|
||||
if (detection.metadata != null) {
|
||||
telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name));
|
||||
telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z));
|
||||
telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw));
|
||||
telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation));
|
||||
} else {
|
||||
telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id));
|
||||
telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y));
|
||||
}
|
||||
} // end for() loop
|
||||
|
||||
} // end method telemetryAprilTag()
|
||||
|
||||
/**
|
||||
* Add telemetry about TensorFlow Object Detection (TFOD) recognitions.
|
||||
*/
|
||||
private void telemetryTfod() {
|
||||
List<Recognition> currentRecognitions = tfod.getRecognitions();
|
||||
telemetry.addData("# Objects Detected", currentRecognitions.size());
|
||||
|
||||
// Step through the list of recognitions and display info for each one.
|
||||
for (Recognition recognition : currentRecognitions) {
|
||||
double x = (recognition.getLeft() + recognition.getRight()) / 2 ;
|
||||
double y = (recognition.getTop() + recognition.getBottom()) / 2 ;
|
||||
|
||||
telemetry.addData(""," ");
|
||||
telemetry.addData("Image", "%s (%.0f %% Conf.)", recognition.getLabel(), recognition.getConfidence() * 100);
|
||||
telemetry.addData("- Position", "%.0f / %.0f", x, y);
|
||||
telemetry.addData("- Size", "%.0f x %.0f", recognition.getWidth(), recognition.getHeight());
|
||||
} // end for() loop
|
||||
|
||||
} // end method telemetryTfod()
|
||||
|
||||
} // end class
|
@ -83,7 +83,7 @@ public class ConceptExternalHardwareClass extends LinearOpMode {
|
||||
robot.init();
|
||||
|
||||
// Send telemetry message to signify robot waiting;
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
waitForStart();
|
||||
|
||||
// run until the end of the match (driver presses STOP)
|
||||
|
@ -0,0 +1,123 @@
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
/*
|
||||
Copyright (c) 2021-24 Alan Smith
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name of Alan Smith nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import com.qualcomm.hardware.sparkfun.SparkFunLEDStick;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.util.Range;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use the SparkFun QWIIC LED Strip
|
||||
*
|
||||
* This is a simple way to add a strip of 10 LEDs to your robot where you can set the color of each
|
||||
* LED or the whole strip. This allows for driver feedback or even just fun ways to show your team
|
||||
* colors.
|
||||
*
|
||||
* Why?
|
||||
* Because more LEDs == more fun!!
|
||||
*
|
||||
* This OpMode assumes that the QWIIC LED Stick is attached to an I2C interface named "back_leds" in the robot configuration.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list
|
||||
*
|
||||
* You can buy this product here: https://www.sparkfun.com/products/18354
|
||||
* Don't forget to also buy this to make it easy to connect to your Control or Expansion Hub:
|
||||
* https://www.sparkfun.com/products/25596
|
||||
*/
|
||||
@TeleOp(name = "Concept: LED Stick", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptLEDStick extends OpMode {
|
||||
private boolean wasUp;
|
||||
private boolean wasDown;
|
||||
private int brightness = 5; // this needs to be between 0 and 31
|
||||
private final static double END_GAME_TIME = 120 - 30;
|
||||
|
||||
private SparkFunLEDStick ledStick;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
ledStick = hardwareMap.get(SparkFunLEDStick.class, "back_leds");
|
||||
ledStick.setBrightness(brightness);
|
||||
ledStick.setColor(Color.GREEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
resetRuntime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loop() {
|
||||
telemetry.addLine("Hold the A button to turn blue");
|
||||
telemetry.addLine("Hold the B button to turn red");
|
||||
telemetry.addLine("Hold the left bumper to turn off");
|
||||
telemetry.addLine("Use DPAD Up/Down to change brightness");
|
||||
|
||||
if (getRuntime() > END_GAME_TIME) {
|
||||
int[] ledColors = {Color.RED, Color.YELLOW, Color.RED, Color.YELLOW, Color.RED,
|
||||
Color.YELLOW, Color.RED, Color.YELLOW, Color.RED, Color.YELLOW};
|
||||
ledStick.setColors(ledColors);
|
||||
} else if (gamepad1.a) {
|
||||
ledStick.setColor(Color.BLUE);
|
||||
} else if (gamepad1.b) {
|
||||
ledStick.setColor(Color.RED);
|
||||
} else if (gamepad1.left_bumper) {
|
||||
ledStick.turnAllOff();
|
||||
} else {
|
||||
ledStick.setColor(Color.GREEN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Use DPAD up and down to change brightness
|
||||
*/
|
||||
int newBrightness = brightness;
|
||||
if (gamepad1.dpad_up && !wasUp) {
|
||||
newBrightness = brightness + 1;
|
||||
} else if (gamepad1.dpad_down && !wasDown) {
|
||||
newBrightness = brightness - 1;
|
||||
}
|
||||
if (newBrightness != brightness) {
|
||||
brightness = Range.clip(newBrightness, 0, 31);
|
||||
ledStick.setBrightness(brightness);
|
||||
}
|
||||
telemetry.addData("Brightness", brightness);
|
||||
|
||||
wasDown = gamepad1.dpad_down;
|
||||
wasUp = gamepad1.dpad_up;
|
||||
}
|
||||
}
|
@ -110,7 +110,7 @@ public class ConceptMotorBulkRead extends LinearOpMode {
|
||||
|
||||
ElapsedTime timer = new ElapsedTime();
|
||||
|
||||
telemetry.addData(">", "Press play to start tests");
|
||||
telemetry.addData(">", "Press START to start tests");
|
||||
telemetry.addData(">", "Test results will update for each access method.");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
@ -53,7 +53,7 @@ public class ConceptNullOp extends OpMode {
|
||||
|
||||
/**
|
||||
* This method will be called repeatedly during the period between when
|
||||
* the init button is pressed and when the play button is pressed (or the
|
||||
* the INIT button is pressed and when the START button is pressed (or the
|
||||
* OpMode is stopped).
|
||||
*/
|
||||
@Override
|
||||
@ -61,7 +61,7 @@ public class ConceptNullOp extends OpMode {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called once, when the play button is pressed.
|
||||
* This method will be called once, when the START button is pressed.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
@ -70,7 +70,7 @@ public class ConceptNullOp extends OpMode {
|
||||
|
||||
/**
|
||||
* This method will be called repeatedly during the period between when
|
||||
* the play button is pressed and when the OpMode is stopped.
|
||||
* the START button is pressed and when the OpMode is stopped.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright (c) 2024 Alan Smith
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
the following conditions are met:
|
||||
Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
Neither the name of Alan Smith nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use the REV Digital Indicator
|
||||
*
|
||||
* This is a simple way to add the REV Digital Indicator which has a red and green LED.
|
||||
* (and as you may remember, red + green = yellow so when they are both on you can get yellow)
|
||||
*
|
||||
* Why?
|
||||
* You can use this to show information to the driver. For example, green might be that you can
|
||||
* pick up more game elements and red would be that you already have the possession limit.
|
||||
*
|
||||
* This OpMode assumes that the REV Digital Indicator is setup as 2 Digital Channels named
|
||||
* front_led_green and front_led_red. (the green should be the lower of the 2 channels it is plugged
|
||||
* into and the red should be the higher)
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list
|
||||
*
|
||||
* You can buy this product here: https://www.revrobotics.com/rev-31-2010/
|
||||
*/
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.LED;
|
||||
|
||||
@TeleOp(name = "Concept: RevLED", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptRevLED extends OpMode {
|
||||
LED frontLED_red;
|
||||
LED frontLED_green;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
frontLED_green = hardwareMap.get(LED.class, "front_led_green");
|
||||
frontLED_red = hardwareMap.get(LED.class, "front_led_red");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loop() {
|
||||
if (gamepad1.a) {
|
||||
frontLED_red.on();
|
||||
} else {
|
||||
frontLED_red.off();
|
||||
}
|
||||
if (gamepad1.b) {
|
||||
frontLED_green.on();
|
||||
} else {
|
||||
frontLED_green.off();
|
||||
}
|
||||
}
|
||||
}
|
@ -38,8 +38,9 @@ import com.qualcomm.robotcore.util.Range;
|
||||
|
||||
|
||||
/*
|
||||
* This OpMode executes a basic Tank Drive Teleop for a two wheeled robot using two REV SPARKminis.
|
||||
* To use this example, connect two REV SPARKminis into servo ports on the Expansion Hub. On the
|
||||
* This OpMode demonstrates a POV Drive system, with commented-out code for a Tank Drive system,
|
||||
* for a two wheeled robot using two REV SPARKminis.
|
||||
* To use this example, connect two REV SPARKminis into servo ports on the Control Hub. On the
|
||||
* robot configuration, use the drop down list under 'Servos' to select 'REV SPARKmini Controller'
|
||||
* and name them 'left_drive' and 'right_drive'.
|
||||
*
|
||||
@ -62,8 +63,7 @@ public class ConceptRevSPARKMini extends LinearOpMode {
|
||||
telemetry.update();
|
||||
|
||||
// Initialize the hardware variables. Note that the strings used here as parameters
|
||||
// to 'get' must correspond to the names assigned during the robot configuration
|
||||
// step (using the FTC Robot Controller app on the phone).
|
||||
// to 'get' must correspond to the names assigned during robot configuration.
|
||||
leftDrive = hardwareMap.get(DcMotorSimple.class, "left_drive");
|
||||
rightDrive = hardwareMap.get(DcMotorSimple.class, "right_drive");
|
||||
|
||||
@ -72,7 +72,7 @@ public class ConceptRevSPARKMini extends LinearOpMode {
|
||||
leftDrive.setDirection(DcMotorSimple.Direction.FORWARD);
|
||||
rightDrive.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
waitForStart();
|
||||
runtime.reset();
|
||||
|
||||
|
@ -100,7 +100,7 @@ public class ConceptSoundsASJava extends LinearOpMode {
|
||||
telemetry.addData("gold resource", goldFound ? "Found" : "NOT found\n Add gold.wav to /src/main/res/raw" );
|
||||
telemetry.addData("silver resource", silverFound ? "Found" : "Not found\n Add silver.wav to /src/main/res/raw" );
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
telemetry.addData(">", "Press Start to continue");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
@ -1,198 +0,0 @@
|
||||
/* Copyright (c) 2019 FIRST. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
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.hardware.camera.BuiltinCameraDirection;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
import org.firstinspires.ftc.robotcore.external.tfod.Recognition;
|
||||
import org.firstinspires.ftc.vision.VisionPortal;
|
||||
import org.firstinspires.ftc.vision.tfod.TfodProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates the basics of TensorFlow Object Detection,
|
||||
* including Java Builder structures for specifying Vision parameters.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list.
|
||||
*/
|
||||
@TeleOp(name = "Concept: TensorFlow Object Detection", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptTensorFlowObjectDetection extends LinearOpMode {
|
||||
|
||||
private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera
|
||||
|
||||
// TFOD_MODEL_ASSET points to a model file stored in the project Asset location,
|
||||
// this is only used for Android Studio when using models in Assets.
|
||||
private static final String TFOD_MODEL_ASSET = "MyModelStoredAsAsset.tflite";
|
||||
// TFOD_MODEL_FILE points to a model file stored onboard the Robot Controller's storage,
|
||||
// this is used when uploading models directly to the RC using the model upload interface.
|
||||
private static final String TFOD_MODEL_FILE = "/sdcard/FIRST/tflitemodels/myCustomModel.tflite";
|
||||
// Define the labels recognized in the model for TFOD (must be in training order!)
|
||||
private static final String[] LABELS = {
|
||||
"Pixel",
|
||||
};
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private TfodProcessor tfod;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the vision portal.
|
||||
*/
|
||||
private VisionPortal visionPortal;
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
initTfod();
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
if (opModeIsActive()) {
|
||||
while (opModeIsActive()) {
|
||||
|
||||
telemetryTfod();
|
||||
|
||||
// Push telemetry to the Driver Station.
|
||||
telemetry.update();
|
||||
|
||||
// Save CPU resources; can resume streaming when needed.
|
||||
if (gamepad1.dpad_down) {
|
||||
visionPortal.stopStreaming();
|
||||
} else if (gamepad1.dpad_up) {
|
||||
visionPortal.resumeStreaming();
|
||||
}
|
||||
|
||||
// Share the CPU.
|
||||
sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
// Save more CPU resources when camera is no longer needed.
|
||||
visionPortal.close();
|
||||
|
||||
} // end runOpMode()
|
||||
|
||||
/**
|
||||
* Initialize the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private void initTfod() {
|
||||
|
||||
// Create the TensorFlow processor by using a builder.
|
||||
tfod = new TfodProcessor.Builder()
|
||||
|
||||
// With the following lines commented out, the default TfodProcessor Builder
|
||||
// will load the default model for the season. To define a custom model to load,
|
||||
// choose one of the following:
|
||||
// Use setModelAssetName() if the custom TF Model is built in as an asset (AS only).
|
||||
// Use setModelFileName() if you have downloaded a custom team model to the Robot Controller.
|
||||
//.setModelAssetName(TFOD_MODEL_ASSET)
|
||||
//.setModelFileName(TFOD_MODEL_FILE)
|
||||
|
||||
// The following default settings are available to un-comment and edit as needed to
|
||||
// set parameters for custom models.
|
||||
//.setModelLabels(LABELS)
|
||||
//.setIsModelTensorFlow2(true)
|
||||
//.setIsModelQuantized(true)
|
||||
//.setModelInputSize(300)
|
||||
//.setModelAspectRatio(16.0 / 9.0)
|
||||
|
||||
.build();
|
||||
|
||||
// Create the vision portal by using a builder.
|
||||
VisionPortal.Builder builder = new VisionPortal.Builder();
|
||||
|
||||
// Set the camera (webcam vs. built-in RC phone camera).
|
||||
if (USE_WEBCAM) {
|
||||
builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"));
|
||||
} else {
|
||||
builder.setCamera(BuiltinCameraDirection.BACK);
|
||||
}
|
||||
|
||||
// Choose a camera resolution. Not all cameras support all resolutions.
|
||||
//builder.setCameraResolution(new Size(640, 480));
|
||||
|
||||
// Enable the RC preview (LiveView). Set "false" to omit camera monitoring.
|
||||
//builder.enableLiveView(true);
|
||||
|
||||
// Set the stream format; MJPEG uses less bandwidth than default YUY2.
|
||||
//builder.setStreamFormat(VisionPortal.StreamFormat.YUY2);
|
||||
|
||||
// Choose whether or not LiveView stops if no processors are enabled.
|
||||
// If set "true", monitor shows solid orange screen if no processors enabled.
|
||||
// If set "false", monitor shows camera view without annotations.
|
||||
//builder.setAutoStopLiveView(false);
|
||||
|
||||
// Set and enable the processor.
|
||||
builder.addProcessor(tfod);
|
||||
|
||||
// Build the Vision Portal, using the above settings.
|
||||
visionPortal = builder.build();
|
||||
|
||||
// Set confidence threshold for TFOD recognitions, at any time.
|
||||
//tfod.setMinResultConfidence(0.75f);
|
||||
|
||||
// Disable or re-enable the TFOD processor at any time.
|
||||
//visionPortal.setProcessorEnabled(tfod, true);
|
||||
|
||||
} // end method initTfod()
|
||||
|
||||
/**
|
||||
* Add telemetry about TensorFlow Object Detection (TFOD) recognitions.
|
||||
*/
|
||||
private void telemetryTfod() {
|
||||
|
||||
List<Recognition> currentRecognitions = tfod.getRecognitions();
|
||||
telemetry.addData("# Objects Detected", currentRecognitions.size());
|
||||
|
||||
// Step through the list of recognitions and display info for each one.
|
||||
for (Recognition recognition : currentRecognitions) {
|
||||
double x = (recognition.getLeft() + recognition.getRight()) / 2 ;
|
||||
double y = (recognition.getTop() + recognition.getBottom()) / 2 ;
|
||||
|
||||
telemetry.addData(""," ");
|
||||
telemetry.addData("Image", "%s (%.0f %% Conf.)", recognition.getLabel(), recognition.getConfidence() * 100);
|
||||
telemetry.addData("- Position", "%.0f / %.0f", x, y);
|
||||
telemetry.addData("- Size", "%.0f x %.0f", recognition.getWidth(), recognition.getHeight());
|
||||
} // end for() loop
|
||||
|
||||
} // end method telemetryTfod()
|
||||
|
||||
} // end class
|
@ -1,142 +0,0 @@
|
||||
/* Copyright (c) 2019 FIRST. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
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.hardware.camera.BuiltinCameraDirection;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
import org.firstinspires.ftc.robotcore.external.tfod.Recognition;
|
||||
import org.firstinspires.ftc.vision.VisionPortal;
|
||||
import org.firstinspires.ftc.vision.tfod.TfodProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates the basics of TensorFlow Object Detection, using
|
||||
* the easiest way.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list.
|
||||
*/
|
||||
@TeleOp(name = "Concept: TensorFlow Object Detection Easy", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptTensorFlowObjectDetectionEasy extends LinearOpMode {
|
||||
|
||||
private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private TfodProcessor tfod;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the vision portal.
|
||||
*/
|
||||
private VisionPortal visionPortal;
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
initTfod();
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
if (opModeIsActive()) {
|
||||
while (opModeIsActive()) {
|
||||
|
||||
telemetryTfod();
|
||||
|
||||
// Push telemetry to the Driver Station.
|
||||
telemetry.update();
|
||||
|
||||
// Save CPU resources; can resume streaming when needed.
|
||||
if (gamepad1.dpad_down) {
|
||||
visionPortal.stopStreaming();
|
||||
} else if (gamepad1.dpad_up) {
|
||||
visionPortal.resumeStreaming();
|
||||
}
|
||||
|
||||
// Share the CPU.
|
||||
sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
// Save more CPU resources when camera is no longer needed.
|
||||
visionPortal.close();
|
||||
|
||||
} // end runOpMode()
|
||||
|
||||
/**
|
||||
* Initialize the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private void initTfod() {
|
||||
|
||||
// Create the TensorFlow processor the easy way.
|
||||
tfod = TfodProcessor.easyCreateWithDefaults();
|
||||
|
||||
// Create the vision portal the easy way.
|
||||
if (USE_WEBCAM) {
|
||||
visionPortal = VisionPortal.easyCreateWithDefaults(
|
||||
hardwareMap.get(WebcamName.class, "Webcam 1"), tfod);
|
||||
} else {
|
||||
visionPortal = VisionPortal.easyCreateWithDefaults(
|
||||
BuiltinCameraDirection.BACK, tfod);
|
||||
}
|
||||
|
||||
} // end method initTfod()
|
||||
|
||||
/**
|
||||
* Add telemetry about TensorFlow Object Detection (TFOD) recognitions.
|
||||
*/
|
||||
private void telemetryTfod() {
|
||||
|
||||
List<Recognition> currentRecognitions = tfod.getRecognitions();
|
||||
telemetry.addData("# Objects Detected", currentRecognitions.size());
|
||||
|
||||
// Step through the list of recognitions and display info for each one.
|
||||
for (Recognition recognition : currentRecognitions) {
|
||||
double x = (recognition.getLeft() + recognition.getRight()) / 2 ;
|
||||
double y = (recognition.getTop() + recognition.getBottom()) / 2 ;
|
||||
|
||||
telemetry.addData(""," ");
|
||||
telemetry.addData("Image", "%s (%.0f %% Conf.)", recognition.getLabel(), recognition.getConfidence() * 100);
|
||||
telemetry.addData("- Position", "%.0f / %.0f", x, y);
|
||||
telemetry.addData("- Size", "%.0f x %.0f", recognition.getWidth(), recognition.getHeight());
|
||||
} // end for() loop
|
||||
|
||||
} // end method telemetryTfod()
|
||||
|
||||
} // end class
|
@ -1,186 +0,0 @@
|
||||
/* Copyright (c) 2020 FIRST. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
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.ClassFactory;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraName;
|
||||
import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;
|
||||
import org.firstinspires.ftc.robotcore.external.tfod.Recognition;
|
||||
import org.firstinspires.ftc.vision.VisionPortal;
|
||||
import org.firstinspires.ftc.vision.VisionPortal.CameraState;
|
||||
import org.firstinspires.ftc.vision.tfod.TfodProcessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates the basics of TensorFlow Object Detection, using
|
||||
* two webcams.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list.
|
||||
*/
|
||||
@TeleOp(name = "Concept: TensorFlow Object Detection Switchable Cameras", group = "Concept")
|
||||
@Disabled
|
||||
public class ConceptTensorFlowObjectDetectionSwitchableCameras extends LinearOpMode {
|
||||
|
||||
/**
|
||||
* Variables used for switching cameras.
|
||||
*/
|
||||
private WebcamName webcam1, webcam2;
|
||||
private boolean oldLeftBumper;
|
||||
private boolean oldRightBumper;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private TfodProcessor tfod;
|
||||
|
||||
/**
|
||||
* The variable to store our instance of the vision portal.
|
||||
*/
|
||||
private VisionPortal visionPortal;
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
initTfod();
|
||||
|
||||
// Wait for the DS start button to be touched.
|
||||
telemetry.addData("DS preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
if (opModeIsActive()) {
|
||||
while (opModeIsActive()) {
|
||||
|
||||
telemetryCameraSwitching();
|
||||
telemetryTfod();
|
||||
|
||||
// Push telemetry to the Driver Station.
|
||||
telemetry.update();
|
||||
|
||||
// Save CPU resources; can resume streaming when needed.
|
||||
if (gamepad1.dpad_down) {
|
||||
visionPortal.stopStreaming();
|
||||
} else if (gamepad1.dpad_up) {
|
||||
visionPortal.resumeStreaming();
|
||||
}
|
||||
|
||||
doCameraSwitching();
|
||||
|
||||
// Share the CPU.
|
||||
sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
// Save more CPU resources when camera is no longer needed.
|
||||
visionPortal.close();
|
||||
|
||||
} // end runOpMode()
|
||||
|
||||
/**
|
||||
* Initialize the TensorFlow Object Detection processor.
|
||||
*/
|
||||
private void initTfod() {
|
||||
|
||||
// Create the TensorFlow processor by using a builder.
|
||||
tfod = new TfodProcessor.Builder().build();
|
||||
|
||||
webcam1 = hardwareMap.get(WebcamName.class, "Webcam 1");
|
||||
webcam2 = hardwareMap.get(WebcamName.class, "Webcam 2");
|
||||
CameraName switchableCamera = ClassFactory.getInstance()
|
||||
.getCameraManager().nameForSwitchableCamera(webcam1, webcam2);
|
||||
|
||||
// Create the vision portal by using a builder.
|
||||
visionPortal = new VisionPortal.Builder()
|
||||
.setCamera(switchableCamera)
|
||||
.addProcessor(tfod)
|
||||
.build();
|
||||
|
||||
} // end method initTfod()
|
||||
|
||||
/**
|
||||
* Add telemetry about camera switching.
|
||||
*/
|
||||
private void telemetryCameraSwitching() {
|
||||
if (visionPortal.getActiveCamera().equals(webcam1)) {
|
||||
telemetry.addData("activeCamera", "Webcam 1");
|
||||
telemetry.addData("Press RightBumper", "to switch to Webcam 2");
|
||||
} else {
|
||||
telemetry.addData("activeCamera", "Webcam 2");
|
||||
telemetry.addData("Press LeftBumper", "to switch to Webcam 1");
|
||||
}
|
||||
} // end method telemetryCameraSwitching()
|
||||
|
||||
/**
|
||||
* Add telemetry about TensorFlow Object Detection (TFOD) recognitions.
|
||||
*/
|
||||
private void telemetryTfod() {
|
||||
|
||||
List<Recognition> currentRecognitions = tfod.getRecognitions();
|
||||
telemetry.addData("# Objects Detected", currentRecognitions.size());
|
||||
|
||||
// Step through the list of recognitions and display info for each one.
|
||||
for (Recognition recognition : currentRecognitions) {
|
||||
double x = (recognition.getLeft() + recognition.getRight()) / 2 ;
|
||||
double y = (recognition.getTop() + recognition.getBottom()) / 2 ;
|
||||
|
||||
telemetry.addData(""," ");
|
||||
telemetry.addData("Image", "%s (%.0f %% Conf.)", recognition.getLabel(), recognition.getConfidence() * 100);
|
||||
telemetry.addData("- Position", "%.0f / %.0f", x, y);
|
||||
telemetry.addData("- Size", "%.0f x %.0f", recognition.getWidth(), recognition.getHeight());
|
||||
} // end for() loop
|
||||
|
||||
} // end method telemetryTfod()
|
||||
|
||||
/**
|
||||
* Set the active camera according to input from the gamepad.
|
||||
*/
|
||||
private void doCameraSwitching() {
|
||||
if (visionPortal.getCameraState() == CameraState.STREAMING) {
|
||||
// If the left bumper is pressed, use Webcam 1.
|
||||
// If the right bumper is pressed, use Webcam 2.
|
||||
boolean newLeftBumper = gamepad1.left_bumper;
|
||||
boolean newRightBumper = gamepad1.right_bumper;
|
||||
if (newLeftBumper && !oldLeftBumper) {
|
||||
visionPortal.setActiveCamera(webcam1);
|
||||
} else if (newRightBumper && !oldRightBumper) {
|
||||
visionPortal.setActiveCamera(webcam2);
|
||||
}
|
||||
oldLeftBumper = newLeftBumper;
|
||||
oldRightBumper = newRightBumper;
|
||||
}
|
||||
} // end method doCameraSwitching()
|
||||
|
||||
} // end class
|
@ -110,7 +110,7 @@ public class RobotAutoDriveByEncoder_Linear extends LinearOpMode {
|
||||
rightDrive.getCurrentPosition());
|
||||
telemetry.update();
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
waitForStart();
|
||||
|
||||
// Step through each leg of the path,
|
||||
|
@ -65,7 +65,7 @@ import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles;
|
||||
* Notes:
|
||||
*
|
||||
* All angles are referenced to the coordinate-frame that is set whenever resetHeading() is called.
|
||||
* In this sample, the heading is reset when the Start button is touched on the Driver station.
|
||||
* In this sample, the heading is reset when the Start button is touched on the Driver Station.
|
||||
* Note: It would be possible to reset the heading after each move, but this would accumulate steering errors.
|
||||
*
|
||||
* The angle of movement/rotation is assumed to be a standardized rotation around the robot Z axis,
|
||||
@ -124,15 +124,15 @@ public class RobotAutoDriveByGyro_Linear extends LinearOpMode {
|
||||
// These constants define the desired driving/control characteristics
|
||||
// They can/should be tweaked to suit the specific robot drive train.
|
||||
static final double DRIVE_SPEED = 0.4; // Max driving speed for better distance accuracy.
|
||||
static final double TURN_SPEED = 0.2; // Max Turn speed to limit turn rate
|
||||
static final double TURN_SPEED = 0.2; // Max turn speed to limit turn rate.
|
||||
static final double HEADING_THRESHOLD = 1.0 ; // How close must the heading get to the target before moving to next step.
|
||||
// Requiring more accuracy (a smaller number) will often make the turn take longer to get into the final position.
|
||||
// Define the Proportional control coefficient (or GAIN) for "heading control".
|
||||
// We define one value when Turning (larger errors), and the other is used when Driving straight (smaller errors).
|
||||
// Increase these numbers if the heading does not corrects strongly enough (eg: a heavy robot or using tracks)
|
||||
// Increase these numbers if the heading does not correct strongly enough (eg: a heavy robot or using tracks)
|
||||
// Decrease these numbers if the heading does not settle on the correct value (eg: very agile robot with omni wheels)
|
||||
static final double P_TURN_GAIN = 0.02; // Larger is more responsive, but also less stable
|
||||
static final double P_DRIVE_GAIN = 0.03; // Larger is more responsive, but also less stable
|
||||
static final double P_TURN_GAIN = 0.02; // Larger is more responsive, but also less stable.
|
||||
static final double P_DRIVE_GAIN = 0.03; // Larger is more responsive, but also less stable.
|
||||
|
||||
|
||||
@Override
|
||||
@ -317,7 +317,7 @@ public class RobotAutoDriveByGyro_Linear extends LinearOpMode {
|
||||
* <p>
|
||||
* Move will stop once the requested time has elapsed
|
||||
* <p>
|
||||
* This function is useful for giving the robot a moment to stabilize it's heading between movements.
|
||||
* This function is useful for giving the robot a moment to stabilize its heading between movements.
|
||||
*
|
||||
* @param maxTurnSpeed Maximum differential turn speed (range 0 to +1.0)
|
||||
* @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset.
|
||||
|
@ -85,10 +85,10 @@ public class RobotAutoDriveByTime_Linear extends LinearOpMode {
|
||||
telemetry.addData("Status", "Ready to run"); //
|
||||
telemetry.update();
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
waitForStart();
|
||||
|
||||
// Step through each leg of the path, ensuring that the Auto mode has not been stopped along the way
|
||||
// Step through each leg of the path, ensuring that the OpMode has not been stopped along the way.
|
||||
|
||||
// Step 1: Drive forward for 3 seconds
|
||||
leftDrive.setPower(FORWARD_SPEED);
|
||||
|
@ -64,7 +64,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* The code assumes a Robot Configuration with motors named: leftfront_drive and rightfront_drive, leftback_drive and rightback_drive.
|
||||
* The motor directions must be set so a positive power goes forward on all wheels.
|
||||
* This sample assumes that the current game AprilTag Library (usually for the current season) is being loaded by default,
|
||||
* so you should choose to approach a valid tag ID (usually starting at 0)
|
||||
* so you should choose to approach a valid tag ID.
|
||||
*
|
||||
* Under manual control, the left stick will move forward/back & left/right. The right stick will rotate the robot.
|
||||
* Manually drive the robot until it displays Target data on the Driver Station.
|
||||
@ -95,12 +95,12 @@ public class RobotAutoDriveToAprilTagOmni extends LinearOpMode
|
||||
// Set the GAIN constants to control the relationship between the measured position error, and how much power is
|
||||
// applied to the drive motors to correct the error.
|
||||
// Drive = Error * Gain Make these values smaller for smoother control, or larger for a more aggressive response.
|
||||
final double SPEED_GAIN = 0.02 ; // Forward Speed Control "Gain". eg: Ramp up to 50% power at a 25 inch error. (0.50 / 25.0)
|
||||
final double STRAFE_GAIN = 0.015 ; // Strafe Speed Control "Gain". eg: Ramp up to 25% power at a 25 degree Yaw error. (0.25 / 25.0)
|
||||
final double TURN_GAIN = 0.01 ; // Turn Control "Gain". eg: Ramp up to 25% power at a 25 degree error. (0.25 / 25.0)
|
||||
final double SPEED_GAIN = 0.02 ; // Forward Speed Control "Gain". e.g. Ramp up to 50% power at a 25 inch error. (0.50 / 25.0)
|
||||
final double STRAFE_GAIN = 0.015 ; // Strafe Speed Control "Gain". e.g. Ramp up to 37% power at a 25 degree Yaw error. (0.375 / 25.0)
|
||||
final double TURN_GAIN = 0.01 ; // Turn Control "Gain". e.g. Ramp up to 25% power at a 25 degree error. (0.25 / 25.0)
|
||||
|
||||
final double MAX_AUTO_SPEED = 0.5; // Clip the approach speed to this max value (adjust for your robot)
|
||||
final double MAX_AUTO_STRAFE= 0.5; // Clip the approach speed to this max value (adjust for your robot)
|
||||
final double MAX_AUTO_STRAFE= 0.5; // Clip the strafing speed to this max value (adjust for your robot)
|
||||
final double MAX_AUTO_TURN = 0.3; // Clip the turn speed to this max value (adjust for your robot)
|
||||
|
||||
private DcMotor leftFrontDrive = null; // Used to control the left front drive wheel
|
||||
@ -145,7 +145,7 @@ public class RobotAutoDriveToAprilTagOmni extends LinearOpMode
|
||||
|
||||
// Wait for driver to press start
|
||||
telemetry.addData("Camera preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
@ -259,7 +259,7 @@ public class RobotAutoDriveToAprilTagOmni extends LinearOpMode
|
||||
aprilTag = new AprilTagProcessor.Builder().build();
|
||||
|
||||
// Adjust Image Decimation to trade-off detection-range for detection-rate.
|
||||
// eg: Some typical detection data using a Logitech C920 WebCam
|
||||
// e.g. Some typical detection data using a Logitech C920 WebCam
|
||||
// Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second
|
||||
// Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second
|
||||
// Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second
|
||||
|
@ -63,7 +63,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* The code assumes a Robot Configuration with motors named left_drive and right_drive.
|
||||
* The motor directions must be set so a positive power goes forward on both wheels;
|
||||
* This sample assumes that the default AprilTag Library (usually for the current season) is being loaded by default
|
||||
* so you should choose to approach a valid tag ID (usually starting at 0)
|
||||
* so you should choose to approach a valid tag ID.
|
||||
*
|
||||
* Under manual control, the left stick will move forward/back, and the right stick will rotate the robot.
|
||||
* This is called POV Joystick mode, different than Tank Drive (where each joystick controls a wheel).
|
||||
@ -94,8 +94,8 @@ public class RobotAutoDriveToAprilTagTank extends LinearOpMode
|
||||
// Set the GAIN constants to control the relationship between the measured position error, and how much power is
|
||||
// applied to the drive motors to correct the error.
|
||||
// Drive = Error * Gain Make these values smaller for smoother control, or larger for a more aggressive response.
|
||||
final double SPEED_GAIN = 0.02 ; // Speed Control "Gain". eg: Ramp up to 50% power at a 25 inch error. (0.50 / 25.0)
|
||||
final double TURN_GAIN = 0.01 ; // Turn Control "Gain". eg: Ramp up to 25% power at a 25 degree error. (0.25 / 25.0)
|
||||
final double SPEED_GAIN = 0.02 ; // Speed Control "Gain". e.g. Ramp up to 50% power at a 25 inch error. (0.50 / 25.0)
|
||||
final double TURN_GAIN = 0.01 ; // Turn Control "Gain". e.g. Ramp up to 25% power at a 25 degree error. (0.25 / 25.0)
|
||||
|
||||
final double MAX_AUTO_SPEED = 0.5; // Clip the approach speed to this max value (adjust for your robot)
|
||||
final double MAX_AUTO_TURN = 0.25; // Clip the turn speed to this max value (adjust for your robot)
|
||||
@ -135,7 +135,7 @@ public class RobotAutoDriveToAprilTagTank extends LinearOpMode
|
||||
|
||||
// Wait for the driver to press Start
|
||||
telemetry.addData("Camera preview on/off", "3 dots, Camera Stream");
|
||||
telemetry.addData(">", "Touch Play to start OpMode");
|
||||
telemetry.addData(">", "Touch START to start OpMode");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
@ -234,7 +234,7 @@ public class RobotAutoDriveToAprilTagTank extends LinearOpMode
|
||||
aprilTag = new AprilTagProcessor.Builder().build();
|
||||
|
||||
// Adjust Image Decimation to trade-off detection-range for detection-rate.
|
||||
// eg: Some typical detection data using a Logitech C920 WebCam
|
||||
// e.g. Some typical detection data using a Logitech C920 WebCam
|
||||
// Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second
|
||||
// Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second
|
||||
// Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second
|
||||
|
@ -106,7 +106,7 @@ public class RobotAutoDriveToLine_Linear extends LinearOpMode {
|
||||
// A gain of 15 causes a Rev Color Sensor V2 to produce an Alpha value of 1.0 at about 1.5" above the floor.
|
||||
colorSensor.setGain(15);
|
||||
|
||||
// Wait for driver to press PLAY)
|
||||
// Wait for driver to press START)
|
||||
// Abort this loop is started or stopped.
|
||||
while (opModeInInit()) {
|
||||
|
||||
|
@ -96,10 +96,10 @@ public class RobotTeleopPOV_Linear extends LinearOpMode {
|
||||
rightClaw.setPosition(MID_SERVO);
|
||||
|
||||
// Send telemetry message to signify robot waiting;
|
||||
telemetry.addData(">", "Robot Ready. Press Play."); //
|
||||
telemetry.addData(">", "Robot Ready. Press START."); //
|
||||
telemetry.update();
|
||||
|
||||
// Wait for the game to start (driver presses PLAY)
|
||||
// Wait for the game to start (driver presses START)
|
||||
waitForStart();
|
||||
|
||||
// run until the end of the match (driver presses STOP)
|
||||
|
@ -94,25 +94,25 @@ public class RobotTeleopTank_Iterative extends OpMode{
|
||||
rightClaw.setPosition(MID_SERVO);
|
||||
|
||||
// Send telemetry message to signify robot waiting;
|
||||
telemetry.addData(">", "Robot Ready. Press Play."); //
|
||||
telemetry.addData(">", "Robot Ready. Press START."); //
|
||||
}
|
||||
|
||||
/*
|
||||
* Code to run REPEATEDLY after the driver hits INIT, but before they hit PLAY
|
||||
* Code to run REPEATEDLY after the driver hits INIT, but before they hit START
|
||||
*/
|
||||
@Override
|
||||
public void init_loop() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Code to run ONCE when the driver hits PLAY
|
||||
* Code to run ONCE when the driver hits START
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Code to run REPEATEDLY after the driver hits PLAY but before they hit STOP
|
||||
* Code to run REPEATEDLY after the driver hits START but before they hit STOP
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
|
@ -0,0 +1,78 @@
|
||||
/* Copyright (c) 2024 FIRST. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
* the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
* of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
* list of conditions and the following disclaimer in the documentation and/or
|
||||
* other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of FIRST nor the names of its contributors may be used to endorse or
|
||||
* promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
* LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DigitalChannel;
|
||||
|
||||
/*
|
||||
* This OpMode demonstrates how to use a digital channel.
|
||||
*
|
||||
* The OpMode assumes that the digital channel is configured with a name of "digitalTouch".
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list.
|
||||
*/
|
||||
@TeleOp(name = "Sensor: digital channel", group = "Sensor")
|
||||
@Disabled
|
||||
public class SensorDigitalTouch extends LinearOpMode {
|
||||
DigitalChannel digitalTouch; // Digital channel Object
|
||||
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
// get a reference to our touchSensor object.
|
||||
digitalTouch = hardwareMap.get(DigitalChannel.class, "digitalTouch");
|
||||
|
||||
digitalTouch.setMode(DigitalChannel.Mode.INPUT);
|
||||
telemetry.addData("DigitalTouchSensorExample", "Press start to continue...");
|
||||
telemetry.update();
|
||||
|
||||
// wait for the start button to be pressed.
|
||||
waitForStart();
|
||||
|
||||
// while the OpMode is active, loop and read the digital channel.
|
||||
// Note we use opModeIsActive() as our loop condition because it is an interruptible method.
|
||||
while (opModeIsActive()) {
|
||||
|
||||
// button is pressed if value returned is LOW or false.
|
||||
// send the info back to driver station using telemetry function.
|
||||
if (digitalTouch.getState() == false) {
|
||||
telemetry.addData("Button", "PRESSED");
|
||||
} else {
|
||||
telemetry.addData("Button", "NOT PRESSED");
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
}
|
@ -33,13 +33,10 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.hardware.dfrobot.HuskyLens;
|
||||
import com.qualcomm.hardware.rev.Rev2mDistanceSensor;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.DistanceSensor;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
|
||||
import org.firstinspires.ftc.robotcore.internal.system.Deadline;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -51,6 +48,9 @@ import java.util.concurrent.TimeUnit;
|
||||
* detect a number of predefined objects and AprilTags in the 36h11 family, can
|
||||
* recognize colors, and can be trained to detect custom objects. See this website for
|
||||
* documentation: https://wiki.dfrobot.com/HUSKYLENS_V1.0_SKU_SEN0305_SEN0336
|
||||
*
|
||||
* For detailed instructions on how a HuskyLens is used in FTC, please see this tutorial:
|
||||
* https://ftc-docs.firstinspires.org/en/latest/devices/huskylens/huskylens.html
|
||||
*
|
||||
* This sample illustrates how to detect AprilTags, but can be used to detect other types
|
||||
* of objects by changing the algorithm. It assumes that the HuskyLens is configured with
|
||||
@ -110,6 +110,8 @@ public class SensorHuskyLens extends LinearOpMode {
|
||||
* Users, should, in general, explicitly choose the algorithm they want to use
|
||||
* within the OpMode by calling selectAlgorithm() and passing it one of the values
|
||||
* found in the enumeration HuskyLens.Algorithm.
|
||||
*
|
||||
* Other algorithm choices for FTC might be: OBJECT_RECOGNITION, COLOR_RECOGNITION or OBJECT_CLASSIFICATION.
|
||||
*/
|
||||
huskyLens.selectAlgorithm(HuskyLens.Algorithm.TAG_RECOGNITION);
|
||||
|
||||
@ -141,6 +143,15 @@ public class SensorHuskyLens extends LinearOpMode {
|
||||
telemetry.addData("Block count", blocks.length);
|
||||
for (int i = 0; i < blocks.length; i++) {
|
||||
telemetry.addData("Block", blocks[i].toString());
|
||||
/*
|
||||
* Here inside the FOR loop, you could save or evaluate specific info for the currently recognized Bounding Box:
|
||||
* - blocks[i].width and blocks[i].height (size of box, in pixels)
|
||||
* - blocks[i].left and blocks[i].top (edges of box)
|
||||
* - blocks[i].x and blocks[i].y (center location)
|
||||
* - blocks[i].id (Color ID)
|
||||
*
|
||||
* These values have Java type int (integer).
|
||||
*/
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
|
@ -58,7 +58,7 @@ import static com.qualcomm.hardware.rev.RevHubOrientationOnRobot.xyzOrientation;
|
||||
* planes (X/Y, X/Z or Y/Z) OR that the Hub has only been rotated in a range of 90 degree increments.
|
||||
*
|
||||
* Note: if your Hub is mounted Orthogonally (on a orthogonal surface, angled at some multiple of
|
||||
* 90 Degrees) then you should use the simpler SensorImuOrthogonal sample in this folder.
|
||||
* 90 Degrees) then you should use the simpler SensorIMUOrthogonal sample in this folder.
|
||||
*
|
||||
* But... If your Hub is mounted Non-Orthogonally, you must specify one or more rotational angles
|
||||
* that transform a "Default" Hub orientation into your desired orientation. That is what is
|
||||
@ -94,6 +94,9 @@ public class SensorIMUNonOrthogonal extends LinearOpMode
|
||||
* 1) Hub laying flat on a horizontal surface, with the Printed Logo facing UP
|
||||
* 2) Rotated such that the USB ports are facing forward on the robot.
|
||||
*
|
||||
* If you are using a REV External IMU, the "Default" orientation is the same as for a REV Hub, but instead of
|
||||
* the USB ports facing forward, the I2C port faces forward.
|
||||
*
|
||||
* The order that the rotations are performed matters, so this sample shows doing them in the order X, Y, then Z.
|
||||
* For specifying non-orthogonal hub mounting orientations, we must temporarily use axes
|
||||
* defined relative to the Hub itself, instead of the usual Robot Coordinate System axes
|
||||
@ -124,7 +127,7 @@ public class SensorIMUNonOrthogonal extends LinearOpMode
|
||||
*
|
||||
* To get the "Default" hub into this configuration you would just need a single rotation, but around a different axis.
|
||||
* 1) No rotation around the X or Y axes.
|
||||
* 1) Rotate the Hub -30 degrees (Clockwise) around the Z axis, since a positive angle would be Counter Clockwise.
|
||||
* 2) Rotate the Hub -30 degrees (Clockwise) around the Z axis, since a positive angle would be Counter Clockwise.
|
||||
*
|
||||
* So the X,Y,Z rotations would be 0,0,-30
|
||||
*
|
||||
|
@ -55,7 +55,7 @@ import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles;
|
||||
* (X/Y, X/Z or Y/Z) and that the Hub has only been rotated in a range of 90 degree increments.
|
||||
*
|
||||
* Note: if your Hub is mounted on a surface angled at some non-90 Degree multiple (like 30) look at
|
||||
* the alternative SensorImuNonOrthogonal sample in this folder.
|
||||
* the alternative SensorIMUNonOrthogonal sample in this folder.
|
||||
*
|
||||
* This "Orthogonal" requirement means that:
|
||||
*
|
||||
@ -98,6 +98,9 @@ public class SensorIMUOrthogonal extends LinearOpMode
|
||||
* The first parameter specifies the direction the printed logo on the Hub is pointing.
|
||||
* The second parameter specifies the direction the USB connector on the Hub is pointing.
|
||||
* All directions are relative to the robot, and left/right is as-viewed from behind the robot.
|
||||
*
|
||||
* If you are using a REV 9-Axis IMU, you can use the Rev9AxisImuOrientationOnRobot class instead of the
|
||||
* RevHubOrientationOnRobot class, which has an I2cPortFacingDirection instead of a UsbFacingDirection.
|
||||
*/
|
||||
|
||||
/* The next two lines define Hub orientation.
|
||||
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Copyright (c) 2024 Limelight Vision
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted (subject to the limitations in the disclaimer below) provided that
|
||||
the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name of FIRST nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS
|
||||
LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.hardware.limelightvision.LLResult;
|
||||
import com.qualcomm.hardware.limelightvision.LLResultTypes;
|
||||
import com.qualcomm.hardware.limelightvision.LLStatus;
|
||||
import com.qualcomm.hardware.limelightvision.Limelight3A;
|
||||
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.navigation.Pose3D;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use the Limelight3A Vision Sensor.
|
||||
*
|
||||
* @see <a href="https://limelightvision.io/">Limelight</a>
|
||||
*
|
||||
* Notes on configuration:
|
||||
*
|
||||
* The device presents itself, when plugged into a USB port on a Control Hub as an ethernet
|
||||
* interface. A DHCP server running on the Limelight automatically assigns the Control Hub an
|
||||
* ip address for the new ethernet interface.
|
||||
*
|
||||
* Since the Limelight is plugged into a USB port, it will be listed on the top level configuration
|
||||
* activity along with the Control Hub Portal and other USB devices such as webcams. Typically
|
||||
* serial numbers are displayed below the device's names. In the case of the Limelight device, the
|
||||
* Control Hub's assigned ip address for that ethernet interface is used as the "serial number".
|
||||
*
|
||||
* Tapping the Limelight's name, transitions to a new screen where the user can rename the Limelight
|
||||
* and specify the Limelight's ip address. Users should take care not to confuse the ip address of
|
||||
* the Limelight itself, which can be configured through the Limelight settings page via a web browser,
|
||||
* and the ip address the Limelight device assigned the Control Hub and which is displayed in small text
|
||||
* below the name of the Limelight on the top level configuration screen.
|
||||
*/
|
||||
@TeleOp(name = "Sensor: Limelight3A", group = "Sensor")
|
||||
@Disabled
|
||||
public class SensorLimelight3A extends LinearOpMode {
|
||||
|
||||
private Limelight3A limelight;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException
|
||||
{
|
||||
limelight = hardwareMap.get(Limelight3A.class, "limelight");
|
||||
|
||||
telemetry.setMsTransmissionInterval(11);
|
||||
|
||||
limelight.pipelineSwitch(0);
|
||||
|
||||
/*
|
||||
* Starts polling for data. If you neglect to call start(), getLatestResult() will return null.
|
||||
*/
|
||||
limelight.start();
|
||||
|
||||
telemetry.addData(">", "Robot Ready. Press Play.");
|
||||
telemetry.update();
|
||||
waitForStart();
|
||||
|
||||
while (opModeIsActive()) {
|
||||
LLStatus status = limelight.getStatus();
|
||||
telemetry.addData("Name", "%s",
|
||||
status.getName());
|
||||
telemetry.addData("LL", "Temp: %.1fC, CPU: %.1f%%, FPS: %d",
|
||||
status.getTemp(), status.getCpu(),(int)status.getFps());
|
||||
telemetry.addData("Pipeline", "Index: %d, Type: %s",
|
||||
status.getPipelineIndex(), status.getPipelineType());
|
||||
|
||||
LLResult result = limelight.getLatestResult();
|
||||
if (result != null) {
|
||||
// Access general information
|
||||
Pose3D botpose = result.getBotpose();
|
||||
double captureLatency = result.getCaptureLatency();
|
||||
double targetingLatency = result.getTargetingLatency();
|
||||
double parseLatency = result.getParseLatency();
|
||||
telemetry.addData("LL Latency", captureLatency + targetingLatency);
|
||||
telemetry.addData("Parse Latency", parseLatency);
|
||||
telemetry.addData("PythonOutput", java.util.Arrays.toString(result.getPythonOutput()));
|
||||
|
||||
if (result.isValid()) {
|
||||
telemetry.addData("tx", result.getTx());
|
||||
telemetry.addData("txnc", result.getTxNC());
|
||||
telemetry.addData("ty", result.getTy());
|
||||
telemetry.addData("tync", result.getTyNC());
|
||||
|
||||
telemetry.addData("Botpose", botpose.toString());
|
||||
|
||||
// Access barcode results
|
||||
List<LLResultTypes.BarcodeResult> barcodeResults = result.getBarcodeResults();
|
||||
for (LLResultTypes.BarcodeResult br : barcodeResults) {
|
||||
telemetry.addData("Barcode", "Data: %s", br.getData());
|
||||
}
|
||||
|
||||
// Access classifier results
|
||||
List<LLResultTypes.ClassifierResult> classifierResults = result.getClassifierResults();
|
||||
for (LLResultTypes.ClassifierResult cr : classifierResults) {
|
||||
telemetry.addData("Classifier", "Class: %s, Confidence: %.2f", cr.getClassName(), cr.getConfidence());
|
||||
}
|
||||
|
||||
// Access detector results
|
||||
List<LLResultTypes.DetectorResult> detectorResults = result.getDetectorResults();
|
||||
for (LLResultTypes.DetectorResult dr : detectorResults) {
|
||||
telemetry.addData("Detector", "Class: %s, Area: %.2f", dr.getClassName(), dr.getTargetArea());
|
||||
}
|
||||
|
||||
// Access fiducial results
|
||||
List<LLResultTypes.FiducialResult> fiducialResults = result.getFiducialResults();
|
||||
for (LLResultTypes.FiducialResult fr : fiducialResults) {
|
||||
telemetry.addData("Fiducial", "ID: %d, Family: %s, X: %.2f, Y: %.2f", fr.getFiducialId(), fr.getFamily(),fr.getTargetXDegrees(), fr.getTargetYDegrees());
|
||||
}
|
||||
|
||||
// Access color results
|
||||
List<LLResultTypes.ColorResult> colorResults = result.getColorResults();
|
||||
for (LLResultTypes.ColorResult cr : colorResults) {
|
||||
telemetry.addData("Color", "X: %.2f, Y: %.2f", cr.getTargetXDegrees(), cr.getTargetYDegrees());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
telemetry.addData("Limelight", "No data available");
|
||||
}
|
||||
|
||||
telemetry.update();
|
||||
}
|
||||
limelight.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2024 DigitalChickenLabs
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.hardware.digitalchickenlabs.OctoQuad;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module
|
||||
*
|
||||
* The OctoQuad has 8 input channels that can used to read either Relative Quadrature Encoders or Pulse-Width Absolute Encoder inputs.
|
||||
* Relative Quadrature encoders are found on most FTC motors, and some stand-alone position sensors like the REV Thru-Bore encoder.
|
||||
* Pulse-Width encoders are less common. The REV Thru-Bore encoder can provide its absolute position via a variable pulse width,
|
||||
* as can several sonar rangefinders such as the MaxBotix MB1000 series.
|
||||
*
|
||||
* This basic sample shows how an OctoQuad can be used to read the position three Odometry pods fitted
|
||||
* with REV Thru-Bore encoders. For a more advanced example showing additional OctoQuad capabilities, see the SensorOctoQuadAdv sample.
|
||||
*
|
||||
* This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration.
|
||||
*
|
||||
* The code assumes the first three OctoQuad inputs are connected as follows
|
||||
* - Chan 0: for measuring forward motion on the left side of the robot.
|
||||
* - Chan 1: for measuring forward motion on the right side of the robot.
|
||||
* - Chan 2: for measuring Lateral (strafing) motion.
|
||||
*
|
||||
* The encoder values may be reset to zero by pressing the X (left most) button on Gamepad 1.
|
||||
*
|
||||
* This sample does not show how to interpret these readings, just how to obtain and display them.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list
|
||||
*
|
||||
* See the sensor's product page: https://www.tindie.com/products/35114/
|
||||
*/
|
||||
@TeleOp(name = "OctoQuad Basic", group="OctoQuad")
|
||||
@Disabled
|
||||
public class SensorOctoQuad extends LinearOpMode {
|
||||
|
||||
// Identify which encoder OctoQuad inputs are connected to each odometry pod.
|
||||
private final int ODO_LEFT = 0; // Facing forward direction on left side of robot (Axial motion)
|
||||
private final int ODO_RIGHT = 1; // Facing forward direction on right side or robot (Axial motion)
|
||||
private final int ODO_PERP = 2; // Facing perpendicular direction at the center of the robot (Lateral motion)
|
||||
|
||||
// Declare the OctoQuad object and members to store encoder positions and velocities
|
||||
private OctoQuad octoquad;
|
||||
|
||||
private int posLeft;
|
||||
private int posRight;
|
||||
private int posPerp;
|
||||
|
||||
/**
|
||||
* This function is executed when this OpMode is selected from the Driver Station.
|
||||
*/
|
||||
@Override
|
||||
public void runOpMode() {
|
||||
|
||||
// Connect to OctoQuad by referring to its name in the Robot Configuration.
|
||||
octoquad = hardwareMap.get(OctoQuad.class, "octoquad");
|
||||
|
||||
// Read the Firmware Revision number from the OctoQuad and display it as telemetry.
|
||||
telemetry.addData("OctoQuad Firmware Version ", octoquad.getFirmwareVersion());
|
||||
|
||||
// Reverse the count-direction of any encoder that is not what you require.
|
||||
// e.g. if you push the robot forward and the left encoder counts down, then reverse it so it counts up.
|
||||
octoquad.setSingleEncoderDirection(ODO_LEFT, OctoQuad.EncoderDirection.REVERSE);
|
||||
octoquad.setSingleEncoderDirection(ODO_RIGHT, OctoQuad.EncoderDirection.FORWARD);
|
||||
octoquad.setSingleEncoderDirection(ODO_PERP, OctoQuad.EncoderDirection.FORWARD);
|
||||
|
||||
// Any changes that are made should be saved in FLASH just in case there is a sensor power glitch.
|
||||
octoquad.saveParametersToFlash();
|
||||
|
||||
telemetry.addLine("\nPress START to read encoder values");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
// Configure the telemetry for optimal display of data.
|
||||
telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE);
|
||||
telemetry.setMsTransmissionInterval(50);
|
||||
|
||||
// Set all the encoder inputs to zero.
|
||||
octoquad.resetAllPositions();
|
||||
|
||||
// Loop while displaying the odometry pod positions.
|
||||
while (opModeIsActive()) {
|
||||
telemetry.addData(">", "Press X to Reset Encoders\n");
|
||||
|
||||
// Check for X button to reset encoders.
|
||||
if (gamepad1.x) {
|
||||
// Reset the position of all encoders to zero.
|
||||
octoquad.resetAllPositions();
|
||||
}
|
||||
|
||||
// Read all the encoder data. Load into local members.
|
||||
readOdometryPods();
|
||||
|
||||
// Display the values.
|
||||
telemetry.addData("Left ", "%8d counts", posLeft);
|
||||
telemetry.addData("Right", "%8d counts", posRight);
|
||||
telemetry.addData("Perp ", "%8d counts", posPerp);
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
|
||||
private void readOdometryPods() {
|
||||
// For best performance, we should only perform ONE transaction with the OctoQuad each cycle.
|
||||
// Since this example only needs to read positions from a few channels, we could use either
|
||||
// readPositionRange(idxFirst, idxLast) to get a select number of sequential channels
|
||||
// or
|
||||
// readAllPositions() to get all 8 encoder readings
|
||||
//
|
||||
// Since both calls take almost the same amount of time, and the actual channels may not end up
|
||||
// being sequential, we will read all of the encoder positions, and then pick out the ones we need.
|
||||
int[] positions = octoquad.readAllPositions();
|
||||
posLeft = positions[ODO_LEFT];
|
||||
posRight = positions[ODO_RIGHT];
|
||||
posPerp = positions[ODO_PERP];
|
||||
}
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (c) 2024 DigitalChickenLabs
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.hardware.digitalchickenlabs.OctoQuad;
|
||||
import com.qualcomm.hardware.digitalchickenlabs.OctoQuadBase;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.util.ElapsedTime;
|
||||
import com.qualcomm.robotcore.util.MovingStatistics;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use advanced features of the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module
|
||||
*
|
||||
* The OctoQuad has 8 input channels that can used to read either Quadrature Encoder signals (like with most FTC motors)
|
||||
* or Pulse-Width style Absolute Encoder inputs (eg: REV Through Bore encoder pulse width output).
|
||||
*
|
||||
* This OpMode illustrates several of the more advanced features of an OctoQuad, including Pulse Width measurement and velocity measurement.
|
||||
* For a more basic OpMode just showing how to read encoder inputs, see the SensorOctoQuad sample.
|
||||
*
|
||||
* This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration.
|
||||
*
|
||||
* One system that requires a lot of encoder inputs is a Swerve Drive system.
|
||||
* Such a drive requires two encoders per drive module: one for position & velocity of the Drive motor/wheel, and one for position/angle of the Steering motor.
|
||||
* The Drive motor usually requires a quadrature encoder, and the Steering motor requires an Absolute encoder.
|
||||
* This quantity and combination of encoder inputs is a challenge, but a single OctoQuad could be used to read them all.
|
||||
*
|
||||
* This sample will configure an OctoQuad for a swerve drive, as follows
|
||||
* - Configure 4 Relative Quadrature Encoder inputs and 4 Absolute Pulse-Width Encoder inputs
|
||||
* - Configure a velocity sample interval of 25 mSec
|
||||
* - Configure a pulse width input range of 1-1024 uSec for a REV Through Bore Encoder's Absolute Pulse output.
|
||||
*
|
||||
* An OctoSwerveDrive class will be created to initialize the OctoQuad, and manage the 4 swerve modules.
|
||||
* An OctoSwerveModule class will be created to configure and read a single swerve module.
|
||||
*
|
||||
* Wiring:
|
||||
* The OctoQuad will be configured to accept Quadrature encoders on the first four channels and Absolute (pulse width) encoders on the last four channels.
|
||||
*
|
||||
* The standard 4-pin to 4-pin cable can be used to connect each Driver Motor encoder to the OctoQuad. (channels 0-3)
|
||||
*
|
||||
* A modified version of the REV 6-4 pin cable (shipped with the encoder) connects the steering encoder to the OctoQuad. (channels 4-7)
|
||||
* To connect the Absolute position signal from a REV Thru-Bore encoder to the OctoQuad, the Provided 6-pin to 4-pin cable will need to be modified.
|
||||
* At the 6-pin connector end, move the yellow wire from its initial pin-3 position to the ABS pin-5 position. This can be done easily
|
||||
* by using a small flathead screwdriver to lift the small white tab holding the metal wire crimp in place and gently pulling the wire out.
|
||||
* See the OctoSwerveDrive() constructor below for the correct wheel/channel assignment.
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list
|
||||
*
|
||||
* Note: If you prefer, you can move the two support classes from this file, and place them in their own files.
|
||||
* But leaving them in place is simpler for this example.
|
||||
*
|
||||
* See the sensor's product page: https://www.tindie.com/products/35114/
|
||||
*/
|
||||
@TeleOp(name="OctoQuad Advanced", group="OctoQuad")
|
||||
@Disabled
|
||||
public class SensorOctoQuadAdv extends LinearOpMode {
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
// Connect to the OctoQuad by looking up its name in the hardwareMap.
|
||||
OctoQuad octoquad = hardwareMap.get(OctoQuad.class, "octoquad");
|
||||
|
||||
// Create the interface for the Swerve Drive Encoders
|
||||
OctoSwerveDrive octoSwerveDrive = new OctoSwerveDrive(octoquad);
|
||||
|
||||
// Display the OctoQuad firmware revision
|
||||
telemetry.addLine("OctoQuad Firmware v" + octoquad.getFirmwareVersion());
|
||||
telemetry.addLine("\nPress START to read encoder values");
|
||||
telemetry.update();
|
||||
|
||||
waitForStart();
|
||||
|
||||
// Configure the telemetry for optimal display of data.
|
||||
telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE);
|
||||
telemetry.setMsTransmissionInterval(50);
|
||||
|
||||
// Run stats to determine cycle times.
|
||||
MovingStatistics avgTime = new MovingStatistics(100);
|
||||
ElapsedTime elapsedTime = new ElapsedTime();
|
||||
|
||||
while (opModeIsActive()) {
|
||||
telemetry.addData(">", "Press X to Reset Encoders\n");
|
||||
|
||||
if(gamepad1.x) {
|
||||
octoquad.resetAllPositions();
|
||||
}
|
||||
|
||||
// read all the swerve drive encoders and update positions and velocities.
|
||||
octoSwerveDrive.updateModules();
|
||||
octoSwerveDrive.show(telemetry);
|
||||
|
||||
// Update cycle time stats
|
||||
avgTime.add(elapsedTime.nanoseconds());
|
||||
elapsedTime.reset();
|
||||
|
||||
telemetry.addData("Loop time", "%.1f mS", avgTime.getMean()/1000000);
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ Internal (Inner) Classes =============================
|
||||
|
||||
/***
|
||||
* OctoSwerveDrive class manages 4 Swerve Modules
|
||||
* - Performs general OctoQuad initialization
|
||||
* - Creates 4 module classes, one for each swerve module
|
||||
* - Updates swerve drive status by reading all data from OctoQuad and Updating each module
|
||||
* - Displays all swerve drive data as telemetry
|
||||
*/
|
||||
class OctoSwerveDrive {
|
||||
|
||||
private final OctoQuad octoquad;
|
||||
private final List<OctoSwerveModule> allModules = new ArrayList<>();
|
||||
|
||||
// members to hold encoder data for each module.
|
||||
public final OctoSwerveModule LeftFront;
|
||||
public final OctoSwerveModule RightFront;
|
||||
public final OctoSwerveModule LeftBack;
|
||||
public final OctoSwerveModule RightBack;
|
||||
|
||||
// Prepare an object to hold an entire OctoQuad encoder readable register bank (pos and vel)
|
||||
private final OctoQuad.EncoderDataBlock encoderDataBlock = new OctoQuad.EncoderDataBlock();
|
||||
|
||||
public OctoSwerveDrive(OctoQuad octoquad) {
|
||||
this.octoquad = octoquad;
|
||||
|
||||
// Clear out all prior settings and encoder data before setting up desired configuration
|
||||
octoquad.resetEverything();
|
||||
|
||||
// Assume first 4 channels are relative encoders and the next 4 are absolute encoders
|
||||
octoquad.setChannelBankConfig(OctoQuad.ChannelBankConfig.BANK1_QUADRATURE_BANK2_PULSE_WIDTH);
|
||||
|
||||
// Create the four OctoSwerveModules, and add them to a 'list' for easier reference.
|
||||
|
||||
// Note: The wheel/channel order is set here. Ensure your encoder cables match.
|
||||
//
|
||||
// Note: The angleOffset must be set for each module so a forward facing wheel shows a steer angle of 0 degrees.
|
||||
// The process for determining the correct angleOffsets is as follows:
|
||||
// 1) Set all the angleValues (below) to zero then build and deploy the code.
|
||||
// 2) Physically rotate all the swerve drives so the wheels are facing forward in the desired 0 degree position
|
||||
// 3) Run the OpMode, view the telemetry and record the "Degrees" value for each module.
|
||||
// 4) Update the code by entering the recorded Degrees value for each module as the angleOffset (last) parameter in the lines below.
|
||||
//
|
||||
// Rebuild and deploy the new code. Verify that the telemetry now indicates 0 degrees when the wheels are facing forward.
|
||||
// Also verify that the correct module values change appropriately when you manually spin (drive) and rotate (steer) a wheel.
|
||||
|
||||
allModules.add(LeftFront = new OctoSwerveModule(octoquad, "LF ",0,0));// Drive = 0, Steer = 4
|
||||
allModules.add(RightFront = new OctoSwerveModule(octoquad, "RF ",1,0));// Drive = 1, Steer = 5
|
||||
allModules.add(LeftBack = new OctoSwerveModule(octoquad, "LB ",2,0));// Drive = 2, Steer = 6
|
||||
allModules.add(RightBack = new OctoSwerveModule(octoquad, "RB ",3,0));// Drive = 3, Steer = 7
|
||||
|
||||
// now make sure the settings persist through any power glitches.
|
||||
octoquad.saveParametersToFlash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all 4 swerve modules
|
||||
*/
|
||||
public void updateModules() {
|
||||
// Read full OctoQuad data block and then pass DataBlock to each swerve module to update themselves.
|
||||
octoquad.readAllEncoderData(encoderDataBlock);
|
||||
for (OctoSwerveModule module : allModules) {
|
||||
module.updateModule(encoderDataBlock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate telemetry data for all modules.
|
||||
* @param telemetry OpMode Telemetry object
|
||||
*/
|
||||
public void show(Telemetry telemetry) {
|
||||
// create general header block and then have each module add its own telemetry
|
||||
telemetry.addData("pos", " Count CPS Degree DPS");
|
||||
for (OctoSwerveModule module : allModules) {
|
||||
module.show(telemetry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* The OctoSwerveModule class manages a single swerve module
|
||||
*/
|
||||
class OctoSwerveModule {
|
||||
|
||||
public double driveCounts;
|
||||
public double driveCountsPerSec;
|
||||
public double steerDegrees;
|
||||
public double steerDegreesPerSec;
|
||||
|
||||
private final String name;
|
||||
private final int channel;
|
||||
private final double angleOffset;
|
||||
private final double steerDirMult;
|
||||
|
||||
private static final int VELOCITY_SAMPLE_INTERVAL_MS = 25; // To provide 40 updates per second.
|
||||
private static final double DEGREES_PER_US = (360.0 / 1024.0); // based on REV Through Bore Encoder
|
||||
private static final double VELOCITY_SAMPLES_PER_S = (1000.0 / VELOCITY_SAMPLE_INTERVAL_MS);
|
||||
|
||||
// The correct drive and turn directions must be set for the Swerve Module based on the specific hardware geometry.
|
||||
// Forward motion must generate an increasing drive count.
|
||||
// Counter Clockwise steer rotation must generate an increasing Steer Angle (degrees)
|
||||
private static final boolean INVERT_DRIVE_ENCODER = false; // Set true if forward motion decreases drive "Count"
|
||||
private static final boolean INVERT_STEER_ENCODER = false; // Set true if counter clockwise steer action decreases steer "Degree"
|
||||
|
||||
/***
|
||||
* @param octoquad provide access to configure OctoQuad
|
||||
* @param name name used for telemetry display
|
||||
* @param quadChannel Quadrature encoder channel. Pulse Width channel is this + 4
|
||||
* @param angleOffset Angle to subtract from absolute encoder to calibrate zero position. (see comments above)
|
||||
*/
|
||||
public OctoSwerveModule (OctoQuad octoquad, String name, int quadChannel, double angleOffset) {
|
||||
this.name = name;
|
||||
this.channel = quadChannel;
|
||||
this.angleOffset = angleOffset;
|
||||
this.steerDirMult = INVERT_STEER_ENCODER ? -1 : 1 ; // create a multiplier to flip the steer angle.
|
||||
|
||||
// Set the drive encoder direction. Note the absolute encoder does not have built-in direction inversion.
|
||||
octoquad.setSingleEncoderDirection(channel, INVERT_DRIVE_ENCODER ? OctoQuad.EncoderDirection.REVERSE : OctoQuad.EncoderDirection.FORWARD);
|
||||
|
||||
// Set the velocity sample interval on both encoders
|
||||
octoquad.setSingleVelocitySampleInterval(channel, VELOCITY_SAMPLE_INTERVAL_MS);
|
||||
octoquad.setSingleVelocitySampleInterval(channel + 4, VELOCITY_SAMPLE_INTERVAL_MS);
|
||||
|
||||
// Setup Absolute encoder pulse range to match REV Through Bore encoder.
|
||||
octoquad.setSingleChannelPulseWidthParams (channel + 4, new OctoQuad.ChannelPulseWidthParams(1,1024));
|
||||
}
|
||||
|
||||
/***
|
||||
* Calculate the Swerve module's position and velocity values
|
||||
* @param encoderDataBlock most recent full data block read from OctoQuad.
|
||||
*/
|
||||
public void updateModule(OctoQuad.EncoderDataBlock encoderDataBlock) {
|
||||
driveCounts = encoderDataBlock.positions[channel]; // get Counts.
|
||||
driveCountsPerSec = encoderDataBlock.velocities[channel] * VELOCITY_SAMPLES_PER_S; // convert counts/interval to counts/sec
|
||||
|
||||
// convert uS to degrees. Add in any possible direction flip.
|
||||
steerDegrees = AngleUnit.normalizeDegrees((encoderDataBlock.positions[channel+ 4] * DEGREES_PER_US * steerDirMult) - angleOffset);
|
||||
// convert uS/interval to deg per sec. Add in any possible direction flip.
|
||||
steerDegreesPerSec = encoderDataBlock.velocities[channel + 4] * DEGREES_PER_US * steerDirMult * VELOCITY_SAMPLES_PER_S;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Swerve module's state as telemetry
|
||||
* @param telemetry OpMode Telemetry object
|
||||
*/
|
||||
public void show(Telemetry telemetry) {
|
||||
telemetry.addData(name, "%8.0f %7.0f %7.0f %6.0f", driveCounts, driveCountsPerSec, steerDegrees, steerDegreesPerSec);
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Copyright (c) 2024 SparkFun Electronics
|
||||
*/
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
|
||||
import com.qualcomm.hardware.sparkfun.SparkFunOTOS;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS)
|
||||
*
|
||||
* The OpMode assumes that the sensor is configured with a name of "sensor_otos".
|
||||
*
|
||||
* Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name.
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list
|
||||
*
|
||||
* See the sensor's product page: https://www.sparkfun.com/products/24904
|
||||
*/
|
||||
@TeleOp(name = "Sensor: SparkFun OTOS", group = "Sensor")
|
||||
@Disabled
|
||||
public class SensorSparkFunOTOS extends LinearOpMode {
|
||||
// Create an instance of the sensor
|
||||
SparkFunOTOS myOtos;
|
||||
|
||||
@Override
|
||||
public void runOpMode() throws InterruptedException {
|
||||
// Get a reference to the sensor
|
||||
myOtos = hardwareMap.get(SparkFunOTOS.class, "sensor_otos");
|
||||
|
||||
// All the configuration for the OTOS is done in this helper method, check it out!
|
||||
configureOtos();
|
||||
|
||||
// Wait for the start button to be pressed
|
||||
waitForStart();
|
||||
|
||||
// Loop until the OpMode ends
|
||||
while (opModeIsActive()) {
|
||||
// Get the latest position, which includes the x and y coordinates, plus the
|
||||
// heading angle
|
||||
SparkFunOTOS.Pose2D pos = myOtos.getPosition();
|
||||
|
||||
// Reset the tracking if the user requests it
|
||||
if (gamepad1.y) {
|
||||
myOtos.resetTracking();
|
||||
}
|
||||
|
||||
// Re-calibrate the IMU if the user requests it
|
||||
if (gamepad1.x) {
|
||||
myOtos.calibrateImu();
|
||||
}
|
||||
|
||||
// Inform user of available controls
|
||||
telemetry.addLine("Press Y (triangle) on Gamepad to reset tracking");
|
||||
telemetry.addLine("Press X (square) on Gamepad to calibrate the IMU");
|
||||
telemetry.addLine();
|
||||
|
||||
// Log the position to the telemetry
|
||||
telemetry.addData("X coordinate", pos.x);
|
||||
telemetry.addData("Y coordinate", pos.y);
|
||||
telemetry.addData("Heading angle", pos.h);
|
||||
|
||||
// Update the telemetry on the driver station
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
||||
|
||||
private void configureOtos() {
|
||||
telemetry.addLine("Configuring OTOS...");
|
||||
telemetry.update();
|
||||
|
||||
// Set the desired units for linear and angular measurements. Can be either
|
||||
// meters or inches for linear, and radians or degrees for angular. If not
|
||||
// set, the default is inches and degrees. Note that this setting is not
|
||||
// persisted in the sensor, so you need to set at the start of all your
|
||||
// OpModes if using the non-default value.
|
||||
// myOtos.setLinearUnit(DistanceUnit.METER);
|
||||
myOtos.setLinearUnit(DistanceUnit.INCH);
|
||||
// myOtos.setAngularUnit(AnguleUnit.RADIANS);
|
||||
myOtos.setAngularUnit(AngleUnit.DEGREES);
|
||||
|
||||
// Assuming you've mounted your sensor to a robot and it's not centered,
|
||||
// you can specify the offset for the sensor relative to the center of the
|
||||
// robot. The units default to inches and degrees, but if you want to use
|
||||
// different units, specify them before setting the offset! Note that as of
|
||||
// firmware version 1.0, these values will be lost after a power cycle, so
|
||||
// you will need to set them each time you power up the sensor. For example, if
|
||||
// the sensor is mounted 5 inches to the left (negative X) and 10 inches
|
||||
// forward (positive Y) of the center of the robot, and mounted 90 degrees
|
||||
// clockwise (negative rotation) from the robot's orientation, the offset
|
||||
// would be {-5, 10, -90}. These can be any value, even the angle can be
|
||||
// tweaked slightly to compensate for imperfect mounting (eg. 1.3 degrees).
|
||||
SparkFunOTOS.Pose2D offset = new SparkFunOTOS.Pose2D(0, 0, 0);
|
||||
myOtos.setOffset(offset);
|
||||
|
||||
// Here we can set the linear and angular scalars, which can compensate for
|
||||
// scaling issues with the sensor measurements. Note that as of firmware
|
||||
// version 1.0, these values will be lost after a power cycle, so you will
|
||||
// need to set them each time you power up the sensor. They can be any value
|
||||
// from 0.872 to 1.127 in increments of 0.001 (0.1%). It is recommended to
|
||||
// first set both scalars to 1.0, then calibrate the angular scalar, then
|
||||
// the linear scalar. To calibrate the angular scalar, spin the robot by
|
||||
// multiple rotations (eg. 10) to get a precise error, then set the scalar
|
||||
// to the inverse of the error. Remember that the angle wraps from -180 to
|
||||
// 180 degrees, so for example, if after 10 rotations counterclockwise
|
||||
// (positive rotation), the sensor reports -15 degrees, the required scalar
|
||||
// would be 3600/3585 = 1.004. To calibrate the linear scalar, move the
|
||||
// robot a known distance and measure the error; do this multiple times at
|
||||
// multiple speeds to get an average, then set the linear scalar to the
|
||||
// inverse of the error. For example, if you move the robot 100 inches and
|
||||
// the sensor reports 103 inches, set the linear scalar to 100/103 = 0.971
|
||||
myOtos.setLinearScalar(1.0);
|
||||
myOtos.setAngularScalar(1.0);
|
||||
|
||||
// The IMU on the OTOS includes a gyroscope and accelerometer, which could
|
||||
// have an offset. Note that as of firmware version 1.0, the calibration
|
||||
// will be lost after a power cycle; the OTOS performs a quick calibration
|
||||
// when it powers up, but it is recommended to perform a more thorough
|
||||
// calibration at the start of all your OpModes. Note that the sensor must
|
||||
// be completely stationary and flat during calibration! When calling
|
||||
// calibrateImu(), you can specify the number of samples to take and whether
|
||||
// to wait until the calibration is complete. If no parameters are provided,
|
||||
// it will take 255 samples and wait until done; each sample takes about
|
||||
// 2.4ms, so about 612ms total
|
||||
myOtos.calibrateImu();
|
||||
|
||||
// Reset the tracking algorithm - this resets the position to the origin,
|
||||
// but can also be used to recover from some rare tracking errors
|
||||
myOtos.resetTracking();
|
||||
|
||||
// After resetting the tracking, the OTOS will report that the robot is at
|
||||
// the origin. If your robot does not start at the origin, or you have
|
||||
// another source of location information (eg. vision odometry), you can set
|
||||
// the OTOS location to match and it will continue to track from there.
|
||||
SparkFunOTOS.Pose2D currentPosition = new SparkFunOTOS.Pose2D(0, 0, 0);
|
||||
myOtos.setPosition(currentPosition);
|
||||
|
||||
// Get the hardware and firmware version
|
||||
SparkFunOTOS.Version hwVersion = new SparkFunOTOS.Version();
|
||||
SparkFunOTOS.Version fwVersion = new SparkFunOTOS.Version();
|
||||
myOtos.getVersionInfo(hwVersion, fwVersion);
|
||||
|
||||
telemetry.addLine("OTOS configured! Press start to get position data!");
|
||||
telemetry.addLine();
|
||||
telemetry.addLine(String.format("OTOS Hardware Version: v%d.%d", hwVersion.major, hwVersion.minor));
|
||||
telemetry.addLine(String.format("OTOS Firmware Version: v%d.%d", fwVersion.major, fwVersion.minor));
|
||||
telemetry.update();
|
||||
}
|
||||
}
|
@ -0,0 +1,812 @@
|
||||
/*
|
||||
* Copyright (c) 2024 DigitalChickenLabs
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.firstinspires.ftc.robotcontroller.external.samples;
|
||||
|
||||
import com.qualcomm.hardware.digitalchickenlabs.OctoQuad;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Disabled;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
|
||||
import com.qualcomm.robotcore.hardware.Gamepad;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
|
||||
/*
|
||||
* This OpMode illustrates how to use the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module.
|
||||
*
|
||||
* The OctoQuad has 8 input channels that can used to read either Relative Quadrature Encoders or Pulse-Width Absolute Encoder inputs.
|
||||
* Relative Quadrature encoders are found on most FTC motors, and some stand-alone position sensors like the REV Thru-Bore encoder.
|
||||
* Pulse-Width encoders are less common. The REV Thru-Bore encoder can provide its absolute position via a variable pulse width,
|
||||
* as can several sonar rangefinders such as the MaxBotix MB1000 series.
|
||||
*
|
||||
* This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration.
|
||||
*
|
||||
* Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list
|
||||
*
|
||||
* Select, Init and run the "OctoQuad Configuration Tool" OpMode
|
||||
* Read the blue User-Interface tips at the top of the telemetry screen.
|
||||
* Use the UI buttons to navigate the menu and make any desired changes to the OctoQuad configuration.
|
||||
* Use the Program Settings To FLASH option to make any changes permanent.
|
||||
*
|
||||
* See the sensor's product page: https://www.tindie.com/products/digitalchickenlabs/octoquad-8ch-quadrature-pulse-width-decoder/
|
||||
*/
|
||||
@TeleOp(name = "OctoQuad Configuration Tool", group="OctoQuad")
|
||||
@Disabled
|
||||
public class UtilityOctoQuadConfigMenu extends LinearOpMode
|
||||
{
|
||||
TelemetryMenu.MenuElement rootMenu = new TelemetryMenu.MenuElement("OctoQuad Config Menu", true);
|
||||
TelemetryMenu.MenuElement menuHwInfo = new TelemetryMenu.MenuElement("Hardware Information", false);
|
||||
TelemetryMenu.EnumOption optionI2cResetMode;
|
||||
TelemetryMenu.EnumOption optionChannelBankConfig;
|
||||
|
||||
TelemetryMenu.MenuElement menuEncoderDirections = new TelemetryMenu.MenuElement("Set Encoder Directions", false);
|
||||
TelemetryMenu.BooleanOption[] optionsEncoderDirections = new TelemetryMenu.BooleanOption[OctoQuad.NUM_ENCODERS];
|
||||
|
||||
TelemetryMenu.MenuElement menuVelocityIntervals = new TelemetryMenu.MenuElement("Velocity Measurement Intervals", false);
|
||||
TelemetryMenu.IntegerOption[] optionsVelocityIntervals = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS];
|
||||
|
||||
TelemetryMenu.MenuElement menuAbsParams = new TelemetryMenu.MenuElement("Abs. Encoder Pulse Width Params", false);
|
||||
TelemetryMenu.IntegerOption[] optionsAbsParamsMax = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS];
|
||||
TelemetryMenu.IntegerOption[] optionsAbsParamsMin = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS];
|
||||
|
||||
TelemetryMenu.OptionElement optionProgramToFlash;
|
||||
TelemetryMenu.OptionElement optionSendToRAM;
|
||||
|
||||
TelemetryMenu.StaticClickableOption optionExit;
|
||||
|
||||
OctoQuad octoquad;
|
||||
|
||||
boolean error = false;
|
||||
|
||||
@Override
|
||||
public void runOpMode()
|
||||
{
|
||||
octoquad = hardwareMap.getAll(OctoQuad.class).get(0);
|
||||
|
||||
if(octoquad.getChipId() != OctoQuad.OCTOQUAD_CHIP_ID)
|
||||
{
|
||||
telemetry.addLine("Error: cannot communicate with OctoQuad. Check your wiring and configuration and try again");
|
||||
telemetry.update();
|
||||
|
||||
error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(octoquad.getFirmwareVersion().maj != OctoQuad.SUPPORTED_FW_VERSION_MAJ)
|
||||
{
|
||||
telemetry.addLine("Error: The OctoQuad is running a different major firmware version than this driver was built for. Cannot run configuration tool");
|
||||
telemetry.update();
|
||||
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(error)
|
||||
{
|
||||
waitForStart();
|
||||
return;
|
||||
}
|
||||
|
||||
telemetry.addLine("Retrieving current configuration from OctoQuad");
|
||||
telemetry.update();
|
||||
|
||||
optionExit = new TelemetryMenu.StaticClickableOption("Exit configuration menu")
|
||||
{
|
||||
@Override
|
||||
void onClick() // called on OpMode thread
|
||||
{
|
||||
requestOpModeStop();
|
||||
}
|
||||
};
|
||||
|
||||
optionI2cResetMode = new TelemetryMenu.EnumOption("I2C Reset Mode", OctoQuad.I2cRecoveryMode.values(), octoquad.getI2cRecoveryMode());
|
||||
optionChannelBankConfig = new TelemetryMenu.EnumOption("Channel Bank Modes", OctoQuad.ChannelBankConfig.values(), octoquad.getChannelBankConfig());
|
||||
|
||||
menuHwInfo.addChild(new TelemetryMenu.StaticItem("Board Firmware: v" + octoquad.getFirmwareVersion()));
|
||||
//menuHwInfo.addChild(new TelemetryMenu.StaticItem("Board unique ID: FIXME"));
|
||||
|
||||
for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++)
|
||||
{
|
||||
optionsEncoderDirections[i] = new TelemetryMenu.BooleanOption(
|
||||
String.format("Encoder %d direction", i),
|
||||
octoquad.getSingleEncoderDirection(i) == OctoQuad.EncoderDirection.REVERSE,
|
||||
"-",
|
||||
"+");
|
||||
}
|
||||
menuEncoderDirections.addChildren(optionsEncoderDirections);
|
||||
|
||||
for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++)
|
||||
{
|
||||
optionsVelocityIntervals[i] = new TelemetryMenu.IntegerOption(
|
||||
String.format("Chan %d velocity intvl", i),
|
||||
OctoQuad.MIN_VELOCITY_MEASUREMENT_INTERVAL_MS,
|
||||
OctoQuad.MAX_VELOCITY_MEASUREMENT_INTERVAL_MS,
|
||||
octoquad.getSingleVelocitySampleInterval(i));
|
||||
}
|
||||
menuVelocityIntervals.addChildren(optionsVelocityIntervals);
|
||||
|
||||
for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++)
|
||||
{
|
||||
OctoQuad.ChannelPulseWidthParams params = octoquad.getSingleChannelPulseWidthParams(i);
|
||||
|
||||
optionsAbsParamsMax[i] = new TelemetryMenu.IntegerOption(
|
||||
String.format("Chan %d max pulse length", i),
|
||||
OctoQuad.MIN_PULSE_WIDTH_US,
|
||||
OctoQuad.MAX_PULSE_WIDTH_US,
|
||||
params.max_length_us);
|
||||
|
||||
optionsAbsParamsMin[i] = new TelemetryMenu.IntegerOption(
|
||||
String.format("Chan %d min pulse length", i),
|
||||
OctoQuad.MIN_PULSE_WIDTH_US,
|
||||
OctoQuad.MAX_PULSE_WIDTH_US,
|
||||
params.min_length_us);
|
||||
}
|
||||
menuAbsParams.addChildren(optionsAbsParamsMin);
|
||||
menuAbsParams.addChildren(optionsAbsParamsMax);
|
||||
|
||||
optionProgramToFlash = new TelemetryMenu.OptionElement()
|
||||
{
|
||||
String name = "Program Settings to FLASH";
|
||||
long lastClickTime = 0;
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
if(lastClickTime == 0)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(System.currentTimeMillis() - lastClickTime < 1000)
|
||||
{
|
||||
return name + " **OK**";
|
||||
}
|
||||
else
|
||||
{
|
||||
lastClickTime = 0;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onClick()
|
||||
{
|
||||
sendSettingsToRam();
|
||||
octoquad.saveParametersToFlash();
|
||||
lastClickTime = System.currentTimeMillis();
|
||||
}
|
||||
};
|
||||
|
||||
optionSendToRAM = new TelemetryMenu.OptionElement()
|
||||
{
|
||||
String name = "Send Settings to RAM";
|
||||
long lastClickTime = 0;
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
if(lastClickTime == 0)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(System.currentTimeMillis() - lastClickTime < 1000)
|
||||
{
|
||||
return name + " **OK**";
|
||||
}
|
||||
else
|
||||
{
|
||||
lastClickTime = 0;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onClick()
|
||||
{
|
||||
sendSettingsToRam();
|
||||
lastClickTime = System.currentTimeMillis();
|
||||
}
|
||||
};
|
||||
|
||||
rootMenu.addChild(menuHwInfo);
|
||||
rootMenu.addChild(optionI2cResetMode);
|
||||
rootMenu.addChild(optionChannelBankConfig);
|
||||
rootMenu.addChild(menuEncoderDirections);
|
||||
rootMenu.addChild(menuVelocityIntervals);
|
||||
rootMenu.addChild(menuAbsParams);
|
||||
rootMenu.addChild(optionProgramToFlash);
|
||||
rootMenu.addChild(optionSendToRAM);
|
||||
rootMenu.addChild(optionExit);
|
||||
|
||||
TelemetryMenu menu = new TelemetryMenu(telemetry, rootMenu);
|
||||
|
||||
while (!isStopRequested())
|
||||
{
|
||||
menu.loop(gamepad1);
|
||||
telemetry.update();
|
||||
sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
void sendSettingsToRam()
|
||||
{
|
||||
for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++)
|
||||
{
|
||||
octoquad.setSingleEncoderDirection(i, optionsEncoderDirections[i].getValue() ? OctoQuad.EncoderDirection.REVERSE : OctoQuad.EncoderDirection.FORWARD);
|
||||
octoquad.setSingleVelocitySampleInterval(i, optionsVelocityIntervals[i].getValue());
|
||||
|
||||
OctoQuad.ChannelPulseWidthParams params = new OctoQuad.ChannelPulseWidthParams();
|
||||
params.max_length_us = optionsAbsParamsMax[i].getValue();
|
||||
params.min_length_us = optionsAbsParamsMin[i].getValue();
|
||||
|
||||
octoquad.setSingleChannelPulseWidthParams(i, params);
|
||||
}
|
||||
|
||||
octoquad.setI2cRecoveryMode((OctoQuad.I2cRecoveryMode) optionI2cResetMode.getValue());
|
||||
octoquad.setChannelBankConfig((OctoQuad.ChannelBankConfig) optionChannelBankConfig.getValue());
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2023 OpenFTC Team
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
public static class TelemetryMenu
|
||||
{
|
||||
private final MenuElement root;
|
||||
private MenuElement currentLevel;
|
||||
|
||||
private boolean dpadUpPrev;
|
||||
private boolean dpadDnPrev;
|
||||
private boolean dpadRightPrev;
|
||||
private boolean dpadLeftPrev;
|
||||
private boolean xPrev;
|
||||
private boolean lbPrev;
|
||||
|
||||
private int selectedIdx = 0;
|
||||
private Stack<Integer> selectedIdxStack = new Stack<>();
|
||||
|
||||
private final Telemetry telemetry;
|
||||
|
||||
/**
|
||||
* TelemetryMenu constructor
|
||||
* @param telemetry pass in 'telemetry' from your OpMode
|
||||
* @param root the root menu element
|
||||
*/
|
||||
public TelemetryMenu(Telemetry telemetry, MenuElement root)
|
||||
{
|
||||
this.root = root;
|
||||
this.currentLevel = root;
|
||||
this.telemetry = telemetry;
|
||||
|
||||
telemetry.setDisplayFormat(Telemetry.DisplayFormat.HTML);
|
||||
telemetry.setMsTransmissionInterval(50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this from inside your loop to put the current menu state into
|
||||
* telemetry, and process gamepad inputs for navigating the menu
|
||||
* @param gamepad the gamepad you want to use to navigate the menu
|
||||
*/
|
||||
public void loop(Gamepad gamepad)
|
||||
{
|
||||
// Capture current state of the gamepad buttons we care about;
|
||||
// We can only look once or we risk a race condition
|
||||
boolean dpadUp = gamepad.dpad_up;
|
||||
boolean dpadDn = gamepad.dpad_down;
|
||||
boolean dpadRight = gamepad.dpad_right;
|
||||
boolean dpadLeft = gamepad.dpad_left;
|
||||
boolean x = gamepad.x;
|
||||
boolean lb = gamepad.left_bumper;
|
||||
|
||||
// Figure out who our children our at this level
|
||||
// and figure out which item is currently highlighted
|
||||
// with the selection pointer
|
||||
ArrayList<Element> children = currentLevel.children();
|
||||
Element currentSelection = children.get(selectedIdx);
|
||||
|
||||
// Left and right are inputs to the selected item (if it's an Option)
|
||||
if (currentSelection instanceof OptionElement)
|
||||
{
|
||||
if (dpadRight && !dpadRightPrev) // rising edge
|
||||
{
|
||||
((OptionElement) currentSelection).onRightInput();
|
||||
}
|
||||
else if (dpadLeft && !dpadLeftPrev) // rising edge
|
||||
{
|
||||
((OptionElement) currentSelection).onLeftInput();
|
||||
}
|
||||
}
|
||||
|
||||
// Up and down navigate the current selection pointer
|
||||
if (dpadUp && !dpadUpPrev) // rising edge
|
||||
{
|
||||
selectedIdx--; // Move selection pointer up
|
||||
}
|
||||
else if (dpadDn && !dpadDnPrev) // rising edge
|
||||
{
|
||||
selectedIdx++; // Move selection pointer down
|
||||
}
|
||||
|
||||
// Make selected index sane (don't let it go out of bounds) :eyes:
|
||||
if (selectedIdx >= children.size())
|
||||
{
|
||||
selectedIdx = children.size()-1;
|
||||
}
|
||||
else if (selectedIdx < 0)
|
||||
{
|
||||
selectedIdx = 0;
|
||||
}
|
||||
|
||||
// Select: either enter submenu or input to option
|
||||
else if (x && !xPrev) // rising edge
|
||||
{
|
||||
// Select up element
|
||||
if (currentSelection instanceof SpecialUpElement)
|
||||
{
|
||||
// We can only go up if we're not at the root level
|
||||
if (currentLevel != root)
|
||||
{
|
||||
// Restore selection pointer to where it was before
|
||||
selectedIdx = selectedIdxStack.pop();
|
||||
|
||||
// Change to the parent level
|
||||
currentLevel = currentLevel.parent();
|
||||
}
|
||||
}
|
||||
// Input to option
|
||||
else if (currentSelection instanceof OptionElement)
|
||||
{
|
||||
((OptionElement) currentSelection).onClick();
|
||||
}
|
||||
// Enter submenu
|
||||
else if (currentSelection instanceof MenuElement)
|
||||
{
|
||||
// Save our current selection pointer so we can restore it
|
||||
// later if the user navigates back up a level
|
||||
selectedIdxStack.push(selectedIdx);
|
||||
|
||||
// We have no idea what's in the submenu :monkey: so best to
|
||||
// just set the selection pointer to the first element
|
||||
selectedIdx = 0;
|
||||
|
||||
// Now the current level becomes the submenu that the selection
|
||||
// pointer was on
|
||||
currentLevel = (MenuElement) currentSelection;
|
||||
}
|
||||
}
|
||||
|
||||
// Go up a level
|
||||
else if (lb && !lbPrev)
|
||||
{
|
||||
// We can only go up if we're not at the root level
|
||||
if (currentLevel != root)
|
||||
{
|
||||
// Restore selection pointer to where it was before
|
||||
selectedIdx = selectedIdxStack.pop();
|
||||
|
||||
// Change to the parent level
|
||||
currentLevel = currentLevel.parent();
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current button states so that we can look for
|
||||
// the rising edge the next time around the loop :)
|
||||
dpadUpPrev = dpadUp;
|
||||
dpadDnPrev = dpadDn;
|
||||
dpadRightPrev = dpadRight;
|
||||
dpadLeftPrev = dpadLeft;
|
||||
xPrev = x;
|
||||
lbPrev = lb;
|
||||
|
||||
// Start building the text display.
|
||||
// First, we add the static directions for gamepad operation
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("<font color='#119af5' face=monospace>");
|
||||
builder.append("Navigate items.....dpad up/down\n")
|
||||
.append("Select.............X or Square\n")
|
||||
.append("Edit option........dpad left/right\n")
|
||||
.append("Up one level.......left bumper\n");
|
||||
builder.append("</font>");
|
||||
builder.append("\n");
|
||||
|
||||
// Now actually add the menu options. We start by adding the name of the current menu level.
|
||||
builder.append("<font face=monospace>");
|
||||
builder.append("Current Menu: ").append(currentLevel.name).append("\n");
|
||||
|
||||
// Now we loop through all the child elements of this level and add them
|
||||
for (int i = 0; i < children.size(); i++)
|
||||
{
|
||||
// If the selection pointer is at this index, put a green dot in the box :)
|
||||
if (selectedIdx == i)
|
||||
{
|
||||
builder.append("[<font color=green face=monospace>•</font>] ");
|
||||
}
|
||||
// Otherwise, just put an empty box
|
||||
else
|
||||
{
|
||||
builder.append("[ ] ");
|
||||
}
|
||||
|
||||
// Figure out who the selection pointer is pointing at :eyes:
|
||||
Element e = children.get(i);
|
||||
|
||||
// If it's pointing at a submenu, indicate that it's a submenu to the user
|
||||
// by prefixing "> " to the name.
|
||||
if (e instanceof MenuElement)
|
||||
{
|
||||
builder.append("> ");
|
||||
}
|
||||
|
||||
// Finally, add the element's name
|
||||
builder.append(e.getDisplayText());
|
||||
|
||||
// We musn't forget the newline
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
// Don't forget to close the font tag either
|
||||
builder.append("</font>");
|
||||
|
||||
// Build the string!!!! :nerd:
|
||||
String menu = builder.toString();
|
||||
|
||||
// Add it to telemetry
|
||||
telemetry.addLine(menu);
|
||||
}
|
||||
|
||||
public static class MenuElement extends Element
|
||||
{
|
||||
private String name;
|
||||
private ArrayList<Element> children = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new MenuElement; may either be the root menu, or a submenu (set isRoot accordingly)
|
||||
* @param name the name for this menu
|
||||
* @param isRoot whether this is a root menu, or a submenu
|
||||
*/
|
||||
public MenuElement(String name, boolean isRoot)
|
||||
{
|
||||
this.name = name;
|
||||
|
||||
// If it's not the root menu, we add the up one level option as the first element
|
||||
if (!isRoot)
|
||||
{
|
||||
children.add(new SpecialUpElement());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child element to this menu (may either be an Option or another menu)
|
||||
* @param child the child element to add
|
||||
*/
|
||||
public void addChild(Element child)
|
||||
{
|
||||
child.setParent(this);
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple child elements to this menu (may either be option, or another menu)
|
||||
* @param children the children to add
|
||||
*/
|
||||
public void addChildren(Element[] children)
|
||||
{
|
||||
for (Element e : children)
|
||||
{
|
||||
e.setParent(this);
|
||||
this.children.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
private ArrayList<Element> children()
|
||||
{
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class OptionElement extends Element
|
||||
{
|
||||
/**
|
||||
* Override this to get notified when the element is clicked
|
||||
*/
|
||||
void onClick() {}
|
||||
|
||||
/**
|
||||
* Override this to get notified when the element gets a "left edit" input
|
||||
*/
|
||||
protected void onLeftInput() {}
|
||||
|
||||
/**
|
||||
* Override this to get notified when the element gets a "right edit" input
|
||||
*/
|
||||
protected void onRightInput() {}
|
||||
}
|
||||
|
||||
public static class EnumOption extends OptionElement
|
||||
{
|
||||
protected int idx = 0;
|
||||
protected Enum[] e;
|
||||
protected String name;
|
||||
|
||||
public EnumOption(String name, Enum[] e)
|
||||
{
|
||||
this.e = e;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public EnumOption(String name, Enum[] e, Enum def)
|
||||
{
|
||||
this(name, e);
|
||||
idx = def.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeftInput()
|
||||
{
|
||||
idx++;
|
||||
|
||||
if(idx > e.length-1)
|
||||
{
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRightInput()
|
||||
{
|
||||
idx--;
|
||||
|
||||
if(idx < 0)
|
||||
{
|
||||
idx = e.length-1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick()
|
||||
{
|
||||
//onRightInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
return String.format("%s: <font color='#e37c07' face=monospace>%s</font>", name, e[idx].name());
|
||||
}
|
||||
|
||||
public Enum getValue()
|
||||
{
|
||||
return e[idx];
|
||||
}
|
||||
}
|
||||
|
||||
public static class IntegerOption extends OptionElement
|
||||
{
|
||||
protected int i;
|
||||
protected int min;
|
||||
protected int max;
|
||||
protected String name;
|
||||
|
||||
public IntegerOption(String name, int min, int max, int def)
|
||||
{
|
||||
this.name = name;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.i = def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeftInput()
|
||||
{
|
||||
i--;
|
||||
|
||||
if(i < min)
|
||||
{
|
||||
i = max;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRightInput()
|
||||
{
|
||||
i++;
|
||||
|
||||
if(i > max)
|
||||
{
|
||||
i = min;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick()
|
||||
{
|
||||
//onRightInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
return String.format("%s: <font color='#e37c07' face=monospace>%d</font>", name, i);
|
||||
}
|
||||
|
||||
public int getValue()
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
static class BooleanOption extends OptionElement
|
||||
{
|
||||
private String name;
|
||||
private boolean val = true;
|
||||
|
||||
private String customTrue;
|
||||
private String customFalse;
|
||||
|
||||
BooleanOption(String name, boolean def)
|
||||
{
|
||||
this.name = name;
|
||||
this.val = def;
|
||||
}
|
||||
|
||||
BooleanOption(String name, boolean def, String customTrue, String customFalse)
|
||||
{
|
||||
this(name, def);
|
||||
this.customTrue = customTrue;
|
||||
this.customFalse = customFalse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeftInput()
|
||||
{
|
||||
val = !val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRightInput()
|
||||
{
|
||||
val = !val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick()
|
||||
{
|
||||
val = !val;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
String valStr;
|
||||
|
||||
if(customTrue != null && customFalse != null)
|
||||
{
|
||||
valStr = val ? customTrue : customFalse;
|
||||
}
|
||||
else
|
||||
{
|
||||
valStr = val ? "true" : "false";
|
||||
}
|
||||
|
||||
return String.format("%s: <font color='#e37c07' face=monospace>%s</font>", name, valStr);
|
||||
}
|
||||
|
||||
public boolean getValue()
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static class StaticItem extends OptionElement
|
||||
{
|
||||
private String name;
|
||||
|
||||
public StaticItem(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class StaticClickableOption extends OptionElement
|
||||
{
|
||||
private String name;
|
||||
|
||||
public StaticClickableOption(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
abstract void onClick();
|
||||
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Element
|
||||
{
|
||||
private MenuElement parent;
|
||||
|
||||
protected void setParent(MenuElement parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
protected MenuElement parent()
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
|
||||
protected abstract String getDisplayText();
|
||||
}
|
||||
|
||||
private static class SpecialUpElement extends Element
|
||||
{
|
||||
@Override
|
||||
protected String getDisplayText()
|
||||
{
|
||||
return "<font color='#119af5' face=monospace>.. ↰ Up One Level</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,11 @@ Concept: This is a sample OpMode that illustrates performing a specific function
|
||||
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.
|
||||
|
||||
Utility: This sample type is provided as a useful tool, or aide, to perform some specific development task.
|
||||
It is not expected to be something you would include in your own robot code.
|
||||
To use the tool, comment out the @Disabled annotation and build the App.
|
||||
Read the comments found in the sample for an explanation of its intended use.
|
||||
|
||||
After the prefix, other conventions will apply:
|
||||
|
||||
* Sensor class names should constructed as: Sensor - Company - Type
|
||||
|
@ -34,8 +34,6 @@ package org.firstinspires.ftc.robotcontroller.internal;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpModeManager;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpModeRegister;
|
||||
|
||||
import org.firstinspires.ftc.robotcontroller.external.samples.ConceptNullOp;
|
||||
|
||||
/**
|
||||
* {@link FtcOpModeRegister} is responsible for registering OpModes for use in an FTC game.
|
||||
* @see #register(OpModeManager)
|
||||
@ -49,7 +47,7 @@ public class FtcOpModeRegister implements OpModeRegister {
|
||||
* There are two mechanisms by which an OpMode may be registered.
|
||||
*
|
||||
* 1) The preferred method is by means of class annotations in the OpMode itself.
|
||||
* See, for example the class annotations in {@link ConceptNullOp}.
|
||||
* See, for example the class annotations in {@link org.firstinspires.ftc.robotcontroller.external.samples.ConceptNullOp}.
|
||||
*
|
||||
* 2) The other, retired, method is to modify this {@link #register(OpModeManager)}
|
||||
* method to include explicit calls to OpModeManager.register().
|
||||
|
113
README.md
113
README.md
@ -1,6 +1,6 @@
|
||||
## NOTICE
|
||||
|
||||
This repository contains the public FTC SDK for the CENTERSTAGE (2023-2024) competition season.
|
||||
This repository contains the public FTC SDK for the INTO THE DEEP (2024-2025) competition season.
|
||||
|
||||
## Welcome!
|
||||
This GitHub repository contains the source code that is used to build an Android app to control a *FIRST* Tech Challenge competition robot. To use this SDK, download/clone the entire project to your local computer.
|
||||
@ -59,6 +59,117 @@ The readme.md file located in the [/TeamCode/src/main/java/org/firstinspires/ftc
|
||||
|
||||
# Release Information
|
||||
|
||||
## Version 10.0 (20240828-111152)
|
||||
|
||||
### Breaking Changes
|
||||
* Java classes and Blocks for TensorFlow Object Detection have been removed.
|
||||
* `AngularVelocity.unit` which was of type `AngleUnit` has been renamed `AngularVelocity.angleUnit` of type `UnnormalizedAngleUnit`
|
||||
|
||||
### Enhancements
|
||||
* Sample for REV Digital Indicator has been added - ConceptRevLED
|
||||
* Adds support for the [Sparkfun QWIIC LED Stick](https://www.sparkfun.com/products/18354)
|
||||
* To connect it directly, you need this [cable](https://www.sparkfun.com/products/25596)
|
||||
* Adds ConceptLEDStick OpMode
|
||||
* Adds Blocks for colors black, blue, cyan, dkgray, gray, green, ltgray, magenta, red, white, and yellow.
|
||||
* Adds an "evaluate but ignore result" Block that executes the connected block and ignores the result. Allows you to call a function and ignore the return value.
|
||||
* Adds I2C driver for Maxbotix Maxsonar I2CXL sonar rangefinder
|
||||
* Adds Blocks for setPwmEnable, setPwmDisable, and isPwmEnabled for servos and CR servos.
|
||||
* In the Blocks editor: a \n in the ExportToBlocks annotation's comment field is displayed as a line break.
|
||||
* Telemetry has new method setNumDecimalPlaces
|
||||
* Telemetry now formats doubles and floats (not inside objects, just by themselves)
|
||||
* Adds support for the Limelight 3A.
|
||||
* Adds initial support for the REV Servo Hub
|
||||
* Both the Robot Controller and Driver Station need to be updated to version 10.0 in order for Servo Hubs to be
|
||||
configurable as Servo Hubs. If the app on either device is outdated, the Servo Hub will show up as an Expansion Hub,
|
||||
and some functionality will not work as expected. You should wait to create a configuration that includes a Servo Hub
|
||||
until both the Driver Station and Robot Controller apps have been updated to version 10.0.
|
||||
* Updating the Servo Hub's firmware and changing its address can only be done using the REV Hardware Client at this time
|
||||
* Adds support for the REV 9-Axis IMU (REV-31-3332)
|
||||
* The REV 9-Axis IMU is only supported by the [Universal IMU interface](https://ftc-docs.firstinspires.org/en/latest/programming_resources/imu/imu.html)
|
||||
* Adds `Rev9AxisImuOrientationOnRobot` Java class.
|
||||
* If you mentally substitute this IMU's I2C port for the Control Hub's USB ports, `RevHubOrientationOnRobot` is also compatible with this sensor
|
||||
* Adds Blocks for Rev9AxisImuOrientationOnRobot, including RevHubImuOrientationOnRobot.xyzOrientation and RevHubImuOrientationOnRobot.zyxOrientation.
|
||||
* Adds Blocks samples SensorRev9AxisIMUOrthogonal and SensorRev9AxisIMUNonOrthogonal.
|
||||
* Improves Blocks support for RevHubImuOrientationOnRobot.
|
||||
* Adds Blocks for RevHubImuOrientationOnRobot.xyzOrientation and RevHubImuOrientationOnRobot.zyxOrientation.
|
||||
* Adds Blocks samples SensorHubIMUOrthogonal (replaces SensorIMU) and SensorHubIMUNonOrthogonal.
|
||||
* Updates EasyOpenCV, AprilTag, OpenCV, and `libjpeg-turbo` versions
|
||||
* Adds Blocks for max and min that take two numbers.
|
||||
* Adds Blocks OpModes ConceptRevSPARKMini, RobotAutoDriveByEncoder, RobotAutoDriveByGyro, RobotAutoDriveByTime, RobotAutoDriveToAprilTagOmni, and RobotAutoDriveToAprilTagTank.
|
||||
* Two OpModes with the same name now automatically get renamed with the name followed by a "-" and the class name allowing them to both be on the device.
|
||||
* Shows the name of the active configuration on the Manage page of the Robot Controller Console
|
||||
* Updated AprilTag Library for INTO THE DEEP. Notably, `getCurrentGameTagLibrary()` now returns INTO THE DEEP tags.
|
||||
* Adds Blocks for Telemetry.setMsTransmissionInterval and Telemetry.getMsTransmissionInterval.
|
||||
* Adds Blocks sample SensorOctoQuad.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixes a bug where the RevBlinkinLedDriver Blocks were under Actuators in the Blocks editor toolbox. They are now Other Devices.
|
||||
* Fixes a bug where `Exception`s thrown in user code after a stop was requested by the Driver Station would be silently eaten
|
||||
* Fixed a bug where if you asked for `AngularVelocity` in a unit different than the device reported it in, it would normalize it between -PI and PI for radians, and -180 and 180 for degrees.
|
||||
|
||||
## Version 9.2 (20240701-085519)
|
||||
|
||||
### Important Notes
|
||||
* Java classes and Blocks for TensorFlow Object Detection have been deprecated and will be removed in Version 10.0.
|
||||
* The samples that use TensorFlow Object Detection have been removed.
|
||||
|
||||
### Enhancements
|
||||
* Adds explanatory text to failed items on the inspection activities. To view the explanatory text tap the red warning icon for a failed item.
|
||||
* In the Blocks editor: added a new kind of variable set block that sets the variable and also returns the new value.
|
||||
* Changes the way that camera controls behave for a SwitchableCamera. Now, each method (such as getExposure, getMinExposure, getMaxExposure, setExposure for ExposureControl) acts on the currently active camera.
|
||||
* Adds support for the REV USB PS4 Compatible Gamepad (REV-31-2983)
|
||||
* Adds ConceptAprilTagMultiPortal OpMode
|
||||
* Adds support for OctoQuad Quadrature Encoder & Pulse Width Interface Module
|
||||
* Adds the ExportAprilTagLibraryToBlocks annotation that indicates that a static method that returns an AprilTagLibrary is exported to the Blocks programming environment. The corresponding block will appear in the Blocks toolbox along with the built-in tag libraries.
|
||||
* Adds Blocks OpMode ConceptAprilTagOptimizeExposure.
|
||||
* Adds support for the SparkFun Optical Tracking Odometry sensor.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixes https://github.com/FIRST-Tech-Challenge/FtcRobotController/issues/942 where visionPortal.close() can cause an IndexOutOfBoundsError.
|
||||
* Fixes a bug in the blocks editor where collapsed function blocks show a warning "Collapsed blocks contain warnings." when the Blocks OpMode is reopened.
|
||||
* Fixes a bug where the blocks editor wouldn't warn you that you have unsaved changes when you try to leave. This bug was introduced due to a behavior change in Chrome 119.
|
||||
* [Issue #764](https://github.com/FIRST-Tech-Challenge/FtcRobotController/issues/764) - Get gain control returns a null pointer for a switchable camera
|
||||
* Fixes a bug where the correct deadzone for certain gamepads was not applied when Advanced Gamepad Features was enabled
|
||||
|
||||
## Version 9.1 (20240215-115542)
|
||||
|
||||
### Enhancements
|
||||
* Fixes a problem with Blocks: if the user closes a Block's warning balloon, it will still be closed next time the project is opened in the Blocks editor.
|
||||
* In the Blocks editor, an alert concerning missing hardware devices is not shown if all the Blocks that use the missing hardware devices are disabled.
|
||||
* Adds Blocks to support comparing property values CRServo.Direction, DCMotor.Direction, DCMotor.Mode, DCMotor.ZeroPowerBehavior, DigitalChannel.Mode, GyroSensor.HeadingMode, IrSeekerSensor.Mode, and Servo.Direction, to the corresponding enum Block.
|
||||
* Improves OnBotJava auto-import to correctly import classes when used in certain situations.
|
||||
* Improves OnBotJava autocomplete to provide better completion options in most cases.
|
||||
* This fixes an issue where autocomplete would fail if a method with two or more formal parameters was defined.
|
||||
* In OnBotJava, code folding support was added to expand and collapse code sections
|
||||
* In OnBotJava, the copyright header is now automatically collapsed loading new files
|
||||
* For all Blocks OpMode samples, intro comments have been moved to the RunOpMode comment balloon.
|
||||
* The Clean up Blocks command in the Blocks editor now positions function Blocks so their comment balloons don't overlap other function Blocks.
|
||||
* Added Blocks OpMode sample SensorTouch.
|
||||
* Added Java OpMode sample SensorDigitalTouch.
|
||||
* Several improvements to VisionPortal
|
||||
* Adds option to control whether the stream is automatically started following a `.build()` call on a VisionPortal Builder
|
||||
* Adds option to control whether the vision processing statistics overlay is rendered or not
|
||||
* VisionPortals now implement the `CameraStreamSource` interface, allowing multiportal users to select which portal is routed to the DS in INIT by calling CameraStreamServer.getInstance().setSource(visionPortal). Can be selected via gamepad, between Camera Stream sessions.
|
||||
* Add option to `AprilTagProcessor` to suppress calibration warnings
|
||||
* Improves camera calibration warnings
|
||||
* If a calibration is scaled, the resolution it was scaled from will be listed
|
||||
* If calibrations exist with the wrong aspect ratio, the calibrated resolutions will be listed
|
||||
* Fixes race condition which caused app crash when calling `stopStreaming()` immediately followed by `close()` on a VisionPortal
|
||||
* Fixes IllegalStateException when calling `stopStreaming()` immediately after building a VisionPortal
|
||||
* Added FTC Blocks counterparts to new Java methods:
|
||||
* VisionPortal.Builder.setAutoStartStreamOnBuild
|
||||
* VisionPortal.Builder.setShowStatsOverlay
|
||||
* AprilTagProcessor.Builder.setSuppressCalibrationWarnings
|
||||
* CameraStreamServer.setSource
|
||||
|
||||
### Bug Fixes
|
||||
* Fixes a problem where OnBotJava does not apply font size settings to the editor.
|
||||
* Updates EasyOpenCV dependency to v1.7.1
|
||||
* Fixes inability to use EasyOpenCV CameraFactory in OnBotJava
|
||||
* Fixes entire RC app crash when user pipeline throws an exception
|
||||
* Fixes entire RC app crash when user user canvas annotator throws an exception
|
||||
* Use the modern stacktrace display when handling user exceptions instead of the legacy ESTOP telemetry message
|
||||
|
||||
## Version 9.0.1 (20230929-083754)
|
||||
|
||||
### Enhancements
|
||||
|
@ -26,4 +26,7 @@ android {
|
||||
dependencies {
|
||||
implementation project(':FtcRobotController')
|
||||
annotationProcessor files('lib/OpModeAnnotationProcessor.jar')
|
||||
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.7'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.21'
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package org.firstinspires.ftc.teamcode;
|
||||
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple.Direction;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Encoder;
|
||||
|
||||
public class PedroConstants {
|
||||
|
||||
/*
|
||||
Robot parameters
|
||||
*/
|
||||
|
||||
// Robot motor configurations
|
||||
public static final String FRONT_LEFT_MOTOR = "Drive front lt";
|
||||
public static final String BACK_LEFT_MOTOR = "Drive back lt";
|
||||
public static final String FRONT_RIGHT_MOTOR = "Drive front rt";
|
||||
public static final String BACK_RIGHT_MOTOR = "Drive back rt";
|
||||
|
||||
// Robot motor direction
|
||||
public static final Direction FRONT_LEFT_MOTOR_DIRECTION = Direction.REVERSE;
|
||||
public static final Direction BACK_LEFT_MOTOR_DIRECTION = Direction.REVERSE;
|
||||
public static final Direction FRONT_RIGHT_MOTOR_DIRECTION = Direction.FORWARD;
|
||||
public static final Direction BACK_RIGHT_MOTOR_DIRECTION = Direction.FORWARD;
|
||||
|
||||
// Robot IMU configuration
|
||||
public static final String IMU = "imu";
|
||||
|
||||
// Robot IMU placement
|
||||
public static final RevHubOrientationOnRobot.LogoFacingDirection IMU_LOGO_FACING_DIRECTION
|
||||
= RevHubOrientationOnRobot.LogoFacingDirection.DOWN;
|
||||
public static final RevHubOrientationOnRobot.UsbFacingDirection IMU_USB_FACING_DIRECTION
|
||||
= RevHubOrientationOnRobot.UsbFacingDirection.LEFT;
|
||||
|
||||
// Robot encoders
|
||||
public static final String LEFT_ENCODER = "encoder left";
|
||||
public static final String RIGHT_ENCODER = "encoder right";
|
||||
public static final String BACK_ENCODER = "encoder back";
|
||||
|
||||
// Robot encoder direction
|
||||
public static final double LEFT_ENCODER_DIRECTION = Encoder.FORWARD;
|
||||
public static final double RIGHT_ENCODER_DIRECTION = Encoder.FORWARD;
|
||||
public static final double BACK_ENCODER_DIRECTION = Encoder.REVERSE;
|
||||
|
||||
/*
|
||||
Pedro's parameters
|
||||
*/
|
||||
|
||||
// The weight of the robot in Kilograms
|
||||
public static final double ROBOT_WEIGHT_IN_KG = 10.5;
|
||||
|
||||
// Maximum velocity of the robot going forward
|
||||
public static final double ROBOT_SPEED_FORWARD = 72.0693;
|
||||
|
||||
// Maximum velocity of the robot going right
|
||||
public static final double ROBOT_SPEED_LATERAL = 24.1401;
|
||||
|
||||
// Rate of deceleration when power is cut-off when the robot is moving forward
|
||||
public static final double FORWARD_ZERO_POWER_ACCEL = -74.3779;
|
||||
|
||||
// Rate of deceleration when power is cut-off when the robot is moving to the right
|
||||
public static final double LATERAL_ZERO_POWER_ACCEL = -111.8409;
|
||||
|
||||
// Determines how fast your robot will decelerate as a factor of how fast your robot will coast to a stop
|
||||
public static final double ZERO_POWER_ACCEL_MULT = 4;
|
||||
|
||||
/* Centripetal force correction - increase if robot is correcting into the path
|
||||
- decrease if robot is correcting away from the path */
|
||||
public static final double CENTRIPETAL_SCALING = 0.0005;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
# Useful Links
|
||||
|
||||
## Pedro Pathing
|
||||
|
||||
- Pedro Path Generator: `https://pedro-path-generator.vercel.app/`
|
||||
- Pedro Path Overview: `https://www.youtube.com/watch?v=HI7eyLLpCgM`
|
||||
- Pedro Tuning Overview: `https://www.youtube.com/watch?v=3EXX5_KwfVM`
|
@ -0,0 +1,260 @@
|
||||
## Overview
|
||||
This is the localization system developed for the Pedro Pathing path follower. These localizers use
|
||||
the pose exponential method of localization. It's basically a way of turning movements from the
|
||||
robot's coordinate frame to the global coordinate frame. If you're interested in reading more about
|
||||
it, then check out pages 177 - 183 of [Controls Engineering in the FIRST Robotics Competition](https://file.tavsys.net/control/controls-engineering-in-frc.pdf)
|
||||
by Tyler Veness. However, the OTOS localizer uses its own onboard system for calculating localization,
|
||||
which I do not know about.
|
||||
|
||||
## Setting Your Localizer
|
||||
Go to line `70` in the `PoseUpdater` class, and replace the `new ThreeWheelLocalizer(hardwareMap)`
|
||||
with the localizer that applies to you:
|
||||
* If you're using drive encoders, put `new DriveEncoderLocalizer(hardwareMap)`
|
||||
* If you're using two wheel odometry, put `new TwoWheelLocalizer(hardwareMap)`
|
||||
* If you're using three wheel odometry, put `new ThreeWheelLocalizer(hardwareMap)`, so basically
|
||||
don't change it from the default
|
||||
* If you're using three wheel odometry with the IMU, put `new ThreeWheelIMULocalizer(hardwareMap)`
|
||||
* If you're using OTOS, put `new OTOSLocalizer(hardwareMap)`
|
||||
|
||||
## Tuning
|
||||
To start, you'll want to select your localizer of choice. Below, I'll have instructions for the drive
|
||||
encoder localizer, two tracking wheel localizer, the three tracking wheel localizer, the three
|
||||
wheel with IMU localizer, and the OTOS localizer offered in Pedro Pathing. Scroll down to the section
|
||||
that applies to you and follow the directions there.
|
||||
|
||||
# Drive Encoders
|
||||
* First, you'll need all of your drive motors to have encoders attached.
|
||||
* Then, go to `DriveEncoderLocalizer.java`. The motor names are already set, so you don't have to do
|
||||
anything to change the encoder names there.
|
||||
* Then, reverse the direction of any encoders so that all encoders tick up when the robot is moving forward.
|
||||
* Now, you'll have to tune the multipliers. These convert your measurements from encoder ticks into
|
||||
inches or radians, essentially scaling your localizer so that your numbers are accurate to the real
|
||||
world.
|
||||
* First, start with the `Turn Localizer Tuner`. You'll want to position your robot to be facing
|
||||
in a direction you can easily find again, like lining up an edge of the robot against a field tile edge.
|
||||
By default, you should spin the robot for one rotation going counterclockwise. Once you've spun
|
||||
exactly that one rotation, or whatever you set that value to, then the turn multiplier will be shown
|
||||
as the second number shown. The first number is how far the robot thinks you've spun, and the second
|
||||
number is the multiplier you need to have to scale your current readings to your goal of one rotation,
|
||||
or the custom set angle. Feel free to run a few more tests and average the results. Once you have
|
||||
this multiplier, then replace `TURN_TICKS_TO_RADIANS` in the localizer with your multiplier. Make sure
|
||||
you replace the number, not add on or multiply it to the previous number. The tuner takes into
|
||||
account your current multiplier.
|
||||
* Next, go on to `Forward Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches forward. Once you've pushed that far, or whatever
|
||||
you set that value to, then the forward multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `FORWARD_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Finally, go to `Lateral Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches to the right. Once you've pushed that far, or whatever
|
||||
you set that value to, then the lateral multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `STRAFE_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Once you're done with all this, your localizer should be tuned. To test it out, you can go to
|
||||
`Localization Test` and push around or drive around your robot. Go to [FTC Dashboard](http://192.168.43.1:8080/dash)
|
||||
and on the top right, switch the drop down from the default view to the field view. Then, on the bottom
|
||||
left corner, you should see a field and the robot being drawn on the field. You can then move your
|
||||
robot around and see if the movements look accurate on FTC Dashboard. If they don't, then you'll
|
||||
want to re-run some of the previous steps. Otherwise, congrats on tuning your localizer!
|
||||
|
||||
# Two Wheel Localizer
|
||||
* First, you'll need a Control Hub with a working IMU, and two odometry wheels connected to motor
|
||||
encoder ports on a hub.
|
||||
* Then, go to `TwoWheelLocalizer.java`. First, in the constructor, enter in the positions of your
|
||||
tracking wheels relative to the center of the wheels of the robot. The positions are in inches, so
|
||||
convert measurements accordingly. Use the comment above the class declaration to help you with the
|
||||
coordinates.
|
||||
* Next, go to where it tells you to replace the current statements with your encoder ports in the constructor.
|
||||
Replace the `deviceName` parameter with the name of the port that the encoder is connected to. The
|
||||
variable names correspond to which tracking wheel should be connected.
|
||||
* After that, go to the instantiation of the IMU and change the orientation of the IMU to match that
|
||||
of your robot's.
|
||||
* Then, reverse the direction of any encoders so that the forward encoder ticks up when the robot
|
||||
is moving forward and the strafe encoder ticks up when the robot moves right.
|
||||
* Now, you'll have to tune the multipliers. These convert your measurements from encoder ticks into
|
||||
inches or radians, essentially scaling your localizer so that your numbers are accurate to the real
|
||||
world.
|
||||
* You actually won't need the turning tuner for this one, since the IMU in the Control Hub will take
|
||||
care of the heading readings.
|
||||
* First, start with the `Forward Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches forward. Once you've pushed that far, or whatever
|
||||
you set that value to, then the forward multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `FORWARD_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Finally, go to `Lateral Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches to the right. Once you've pushed that far, or whatever
|
||||
you set that value to, then the lateral multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `STRAFE_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Once you're done with all this, your localizer should be tuned. To test it out, you can go to
|
||||
`Localization Test` and push around or drive around your robot. Go to [FTC Dashboard](http://192.168.43.1:8080/dash)
|
||||
and on the top right, switch the drop down from the default view to the field view. Then, on the bottom
|
||||
left corner, you should see a field and the robot being drawn on the field. You can then move your
|
||||
robot around and see if the movements look accurate on FTC Dashboard. If they don't, then you'll
|
||||
want to re-run some of the previous steps. Otherwise, congrats on tuning your localizer!
|
||||
|
||||
# Three Wheel Localizer
|
||||
* First, you'll need three odometry wheels connected to motor encoder ports on a hub.
|
||||
* Then, go to `ThreeWheelLocalizer.java`. First, in the constructor, enter in the positions of your
|
||||
tracking wheels relative to the center of the wheels of the robot. The positions are in inches, so
|
||||
convert measurements accordingly. Use the comment above the class declaration to help you with the
|
||||
coordinates.
|
||||
* Next, go to where it tells you to replace the current statements with your encoder ports in the constructor.
|
||||
Replace the `deviceName` parameter with the name of the port that the encoder is connected to. The
|
||||
variable names correspond to which tracking wheel should be connected.
|
||||
* Then, reverse the direction of any encoders so that the forward encoders tick up when the robot
|
||||
is moving forward and the strafe encoder ticks up when the robot moves right.
|
||||
* First, start with the `Turn Localizer Tuner`. You'll want to position your robot to be facing
|
||||
in a direction you can easily find again, like lining up an edge of the robot against a field tile edge.
|
||||
By default, you should spin the robot for one rotation going counterclockwise. Once you've spun
|
||||
exactly that one rotation, or whatever you set that value to, then the turn multiplier will be shown
|
||||
as the second number shown. The first number is how far the robot thinks you've spun, and the second
|
||||
number is the multiplier you need to have to scale your current readings to your goal of one rotation,
|
||||
or the custom set angle. Feel free to run a few more tests and average the results. Once you have
|
||||
this multiplier, then replace `TURN_TICKS_TO_RADIANS` in the localizer with your multiplier. Make sure
|
||||
you replace the number, not add on or multiply it to the previous number. The tuner takes into
|
||||
account your current multiplier.
|
||||
* Next, go on to `Forward Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches forward. Once you've pushed that far, or whatever
|
||||
you set that value to, then the forward multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `FORWARD_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Finally, go to `Lateral Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches to the right. Once you've pushed that far, or whatever
|
||||
you set that value to, then the lateral multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `STRAFE_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Once you're done with all this, your localizer should be tuned. To test it out, you can go to
|
||||
`Localization Test` and push around or drive around your robot. Go to [FTC Dashboard](http://192.168.43.1:8080/dash)
|
||||
and on the top right, switch the drop down from the default view to the field view. Then, on the bottom
|
||||
left corner, you should see a field and the robot being drawn on the field. You can then move your
|
||||
robot around and see if the movements look accurate on FTC Dashboard. If they don't, then you'll
|
||||
want to re-run some of the previous steps. Otherwise, congrats on tuning your localizer!
|
||||
|
||||
# Three Wheel Localizer with IMU
|
||||
* First, you'll need three odometry wheels connected to motor encoder ports on a hub.
|
||||
* Then, go to `ThreeWheelIMULocalizer.java`. First, in the constructor, enter in the positions of your
|
||||
tracking wheels relative to the center of the wheels of the robot. The positions are in inches, so
|
||||
convert measurements accordingly. Use the comment above the class declaration to help you with the
|
||||
coordinates.
|
||||
* Next, go to where it tells you to replace the current statements with your encoder ports in the constructor.
|
||||
Replace the `deviceName` parameter with the name of the port that the encoder is connected to. The
|
||||
variable names correspond to which tracking wheel should be connected.
|
||||
* After that, go to the instantiation of the IMU and change the orientation of the IMU to match that
|
||||
of your robot's.
|
||||
* Then, reverse the direction of any encoders so that the forward encoders tick up when the robot
|
||||
is moving forward and the strafe encoder ticks up when the robot moves right.
|
||||
* Although heading localization is done mostly through the IMU, the tracking wheels are still used for
|
||||
small angle adjustments for better stability. So, you will still need to tune your turning multiplier.
|
||||
* First, start with the `Turn Localizer Tuner`. Before doing any tuning, go to FTC Dashboard and find
|
||||
the `ThreeWheelIMULocalizer` dropdown and deselect `useIMU`. You'll want to position your robot to be facing
|
||||
in a direction you can easily find again, like lining up an edge of the robot against a field tile edge.
|
||||
By default, you should spin the robot for one rotation going counterclockwise. Once you've spun
|
||||
exactly that one rotation, or whatever you set that value to, then the turn multiplier will be shown
|
||||
as the second number shown. The first number is how far the robot thinks you've spun, and the second
|
||||
number is the multiplier you need to have to scale your current readings to your goal of one rotation,
|
||||
or the custom set angle. Feel free to run a few more tests and average the results. Once you have
|
||||
this multiplier, then replace `TURN_TICKS_TO_RADIANS` in the localizer with your multiplier. Make sure
|
||||
you replace the number, not add on or multiply it to the previous number. The tuner takes into
|
||||
account your current multiplier.
|
||||
* Next, go on to `Forward Localizer Tuner`. You should re-enable `useIMU` at this time. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches forward. Once you've pushed that far, or whatever
|
||||
you set that value to, then the forward multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `FORWARD_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Finally, go to `Lateral Localizer Tuner`. `useIMU` should be enabled for this step. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches to the right. Once you've pushed that far, or whatever
|
||||
you set that value to, then the lateral multiplier will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the multiplier you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this multiplier, then
|
||||
replace `STRAFE_TICKS_TO_INCHES` in the localizer with your multiplier. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current multiplier.
|
||||
* Once you're done with all this, your localizer should be tuned. Make sure that `useIMU` is turned back on. To test it out, you can go to
|
||||
`Localization Test` and push around or drive around your robot. Go to [FTC Dashboard](http://192.168.43.1:8080/dash)
|
||||
and on the top right, switch the drop down from the default view to the field view. Then, on the bottom
|
||||
left corner, you should see a field and the robot being drawn on the field. You can then move your
|
||||
robot around and see if the movements look accurate on FTC Dashboard. If they don't, then you'll
|
||||
want to re-run some of the previous steps. Otherwise, congrats on tuning your localizer!
|
||||
|
||||
# OTOS Localizer
|
||||
* First, you'll need the OTOS connected to an I2C port on a hub. Make sure the film on the sensor is removed.
|
||||
* Then, go to `OTOSLocalizer.java`. First, in the constructor, go to where it tells you to replace
|
||||
the current statement with your OTOS port in the constructor. Replace the `deviceName` parameter
|
||||
with the name of the port that the OTOS is connected to.
|
||||
* Next, enter in the position of your OTOS relative to the center of the wheels of the robot. The
|
||||
positions are in inches, so convert measurements accordingly. Use the comment above the class
|
||||
declaration as well as to help you with the coordinates.
|
||||
* First, start with the `Turn Localizer Tuner`. You'll want to position your robot to be facing
|
||||
in a direction you can easily find again, like lining up an edge of the robot against a field tile edge.
|
||||
By default, you should spin the robot for one rotation going counterclockwise. Once you've spun
|
||||
exactly that one rotation, or whatever you set that value to, then the angular scalar will be shown
|
||||
as the second number shown. The first number is how far the robot thinks you've spun, and the second
|
||||
number is the scalar you need to have to scale your current readings to your goal of one rotation,
|
||||
or the custom set angle. Feel free to run a few more tests and average the results. Once you have
|
||||
this scalar, then replace the angular scalar on line `78` in the localizer with your scalar.
|
||||
Make sure you replace the number, not add on or multiply it to the previous number. The tuner takes into
|
||||
account your current angular scalar.
|
||||
* For this next step, since OTOS only has one linear scalar, you can run either the forward or lateral
|
||||
localizer tuner and the result should be the same. So, you choose which one you want to run.
|
||||
* Option 1: go on to `Forward Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches forward. Once you've pushed that far, or whatever
|
||||
you set that value to, then the linear scalar will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the scalar you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this scalar, then
|
||||
replace the linear scalar on line `77` in the localizer with your scalar. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current scalar.
|
||||
* Option 2: go to `Lateral Localizer Tuner`. You'll want to position a ruler alongside your robot.
|
||||
By default, you'll want to push the robot 30 inches to the right. Once you've pushed that far, or whatever
|
||||
you set that value to, then the linear scalar will be shown as the second number shown. The
|
||||
first number is how far the robot thinks you've gone, and the second number is the scalar you
|
||||
need to have to scale your current readings to your goal of 30 inches, or the custom set distance.
|
||||
Feel free to run a few more tests and average the results. Once you have this scalar, then
|
||||
replace the linear scalar on line `77` in the localizer with your scalar. Make sure you replace the number,
|
||||
not add on or multiply it to the previous number. The tuner takes into account your current scalar.
|
||||
* Once you're done with all this, your localizer should be tuned. To test it out, you can go to
|
||||
`Localization Test` and push around or drive around your robot. Go to [FTC Dashboard](http://192.168.43.1:8080/dash)
|
||||
and on the top right, switch the drop down from the default view to the field view. Then, on the bottom
|
||||
left corner, you should see a field and the robot being drawn on the field. You can then move your
|
||||
robot around and see if the movements look accurate on FTC Dashboard. If they don't, then you'll
|
||||
want to re-run some of the previous steps. Otherwise, congrats on tuning your localizer!
|
||||
|
||||
## Using Road Runner's Localizer
|
||||
Of course, many teams have experience using Road Runner in the past and so have localizers from Road
|
||||
Runner that are tuned. There is an adapter for the Road Runner three wheel localizer to the Pedro
|
||||
Pathing localization system in Pedro Pathing, but it is commented out by default to reduce the number
|
||||
of imports in gradle.
|
||||
|
||||
To re-enable it, go to `RoadRunnerEncoder.java`, `RoadRunnerThreeWheelLocalizer.java`, and `RRToPedroThreeWheelLocalizer.java`
|
||||
and hit `ctrl` + `a` to select everything within the files. Then, press `ctrl` + `/` to uncomment the code.
|
||||
|
||||
Afterwards, go to `build.gradle` file under the `teamcode` folder and add the following dependencies:
|
||||
```
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'com.acmerobotics.com.roadrunner:core:0.5.6'
|
||||
```
|
||||
|
||||
After that, you should be good to go. If you want to use a different localizer from Road Runner, then
|
||||
you can adapt it in the same process that's used for the Road Runner three wheel localizer.
|
@ -0,0 +1,108 @@
|
||||
## Basic Ideas
|
||||
Pedro Pathing is a reactive vector based follower. What this means is that the robot dynamically
|
||||
calculates a set of vectors that are required to correct error as well as to move forward and applies them.
|
||||
|
||||
The robot calculates:
|
||||
|
||||
* centripetal force correction
|
||||
* translational correction
|
||||
* heading correction
|
||||
* drive vector
|
||||
|
||||
These are then applied to the robot in this order until either the robot's power is maxed out or all
|
||||
the vectors are applied.
|
||||
|
||||
## Why Pedro Pathing?
|
||||
Why use Pedro Pathing? Why not something else like Road Runner or Pure Pursuit?
|
||||
|
||||
* Why not Pure Pursuit?
|
||||
* Pure Pursuit searches for the farthest point on the path that's within a certain radius from the robot. Pure Pursuit will then go in a straight line to that point. This poses several problems, as a small search radius will cause some oscillations on corners, and a large search radius will cut corners on paths, which makes the paths inaccurate to real life.
|
||||
* Pedro Pathing instead corrects to the closest point on the path while still following the path. This ensures that the follower will stay on the path while still being able to move forward along the path without cutting corners or encountering oscillation issues.
|
||||
* Why not Road Runner?
|
||||
* Road Runner is a motion profile based follower, which means that a set of instructions for motor powers are calculated for each path beforehand and then run. After reaching the end of this motion profile, Road Runner corrects. This can be sufficient for most situations, but if the robot encounters an obstacle or wheel slippage, it may be unable to correct in time.
|
||||
* Pedro Pathing instead dynamically corrects throughout the path. The movement vectors are calculated at every point along the path, and because of this, the path can even be changed midway through and Pedro Pathing will still be able to correct. Since correction occurs throughout the path, the error correction isn't concentrated on the end of the path and therefore the robot is able to better minimize error.
|
||||
|
||||
## How Does Pedro Path?
|
||||
As mentioned in the *Basic Ideas* section, Pedro Pathing calculates a set of vectors to move the
|
||||
robot along a path, which is defined with Bezier curves. Here, we'll go more in-depth on how these
|
||||
vectors are calculated and used in path following.
|
||||
|
||||
### The Hierarchy
|
||||
While following paths, sometimes all these motion vectors are demanding more power from the robot
|
||||
than it actually has. How do we deal with this?
|
||||
|
||||
Our motion vectors are applied in order of importance, which is the order they appear in within the
|
||||
list in *Basic Ideas*. The centripetal force vector is the highest importance, since it ensures the
|
||||
robot sticks to the path. If the robot is far off the path, then the robot will not drive along the
|
||||
path, and so the centripetal force vector will be reduced in magnitude. This is why ranking the
|
||||
centripetal force correction above the translational correction doesn't produce issues. The next
|
||||
highest, of course, is the translational correction. This corrects strictly the robot's position to
|
||||
the closest point on the path. The reasoning behind this is that it is usually much more important
|
||||
that the robot be on the path, and so avoid potential obstacles, rather than facing the correct
|
||||
direction. The third highest important vector is the heading correction, which turns the robot to
|
||||
the correct angle. This is higher than the drive vector since we don't want to drive forward if the
|
||||
robot isn't facing the correct direction. Finally, the drive vector is applied. This ensures that
|
||||
the robot only continues on the path when there aren't any major issues with the following.
|
||||
|
||||
As each vector is applied, the robot checks to see if the sum of the applied vectors is greater than
|
||||
the power that the drivetrain can produce, which would be 1 motor power. If the magnitude of the
|
||||
combined vectors is greater than 1, then the most recently added vector is scaled down until the
|
||||
combined vectors' magnitude is equal to 1. If all vectors are able to be applied without exceeding
|
||||
the power limit, then all the vectors can just be applied without issue.
|
||||
|
||||
### Centripetal Force Correction
|
||||
Have you ever noticed that your robot seems to want to swing outwards when taking corners? This is
|
||||
due to a lack of centripetal force correction. In order to take curves effectively, your robot must
|
||||
accelerate towards the inside of the curve. If we can approximate the region of the path the robot
|
||||
is at with a circle, then we can use the formula for centripetal force to calculate how much power
|
||||
we need to allocate to approximate a centripetal force.
|
||||
|
||||
Because paths are defined with Bezier curves, we can easily take the first and second derivative of
|
||||
the path, expressed as vectors. We can use that to calculate the curvature of the path, which is the
|
||||
inverse of the length of the radius of the circle we can use to approximate the path. The actual
|
||||
formula for the calculations of the curvature is the cross product of the first derivative and
|
||||
second derivative, divided by the magnitude of the first derivative raised to the power of 3.
|
||||
|
||||
With this, along with the weight of the robot and the velocity of the robot along the path, we can
|
||||
calculate the force necessary to keep the robot on the path, and then tune a scaling factor to turn
|
||||
that force into a corresponding power for the robot.
|
||||
|
||||
### Translational Correction
|
||||
This is as simple as it sounds: this corrects error in the robot's position only. The robot's translational
|
||||
error is corrected with a PID control. The translational correction does not act along the path the
|
||||
robot takes, but instead moves the robot back to the closest point on the path.
|
||||
|
||||
### Heading Correction
|
||||
The heading correction operates very similarly to the translational correction, except this corrects
|
||||
the direction the robot is facing. The heading correction will turn in the closest direction from the
|
||||
robot's current heading to the target heading.
|
||||
|
||||
### Drive Vector
|
||||
The drive vector points in the direction of the tangent of the path and it is responsible for moving
|
||||
the robot along the path. Using basic kinematics equations, we can use the velocity of the robot
|
||||
along the path, the length of path left, and a specified target rate of deceleration to calculate
|
||||
the velocity we should be moving at. Additionally, after finding out the rate of deceleration of the
|
||||
robot under 0 power, we can compensate for that with another kinematics equation. Combining these
|
||||
two lets us control our velocity to both move along the path quickly and brake without overshooting.
|
||||
|
||||
## Additional Capabilities
|
||||
In addition to following paths, Pedro Pathing can be used for a few other purposes.
|
||||
|
||||
### Holding Points
|
||||
Pedro Pathing is also capable of holding a specified position and direction. This can be useful for
|
||||
improving on the end accuracy of paths, providing additional correction time if possible. It can
|
||||
also be useful in cases where contact with other robots might occur. For instance, in the 2022-2023
|
||||
FTC season challenge, Power Play, robots might come into contact when scoring on a contested middle
|
||||
junction. Pedro Pathing would be able to recover and correct from a robot collision, allowing for
|
||||
more consistent scoring.
|
||||
|
||||
### TeleOp Enhancements
|
||||
Finally, Pedro Pathing can be used in TeleOp to enhance driving. With regular mecanum drive, robots
|
||||
will tend to swing out when taking corners. Pedro Pathing can account for that, allowing the robot
|
||||
to take corners more smoothly and efficiently. Using the same localizer as is used in autonomous, a
|
||||
first and second derivative can be estimated from previous positions. Then, with a modified version
|
||||
of the curvature formula, we can estimate a centripetal force correction and apply it under driver
|
||||
control.
|
||||
|
||||
## Questions?
|
||||
If you still have more questions, feel free to contact us at `scottsbots10158@gmail.com`
|
@ -0,0 +1,28 @@
|
||||
## Welcome!
|
||||
This is the Pedro Pathing path following program developed by FTC team 10158 Scott's Bots with Logan
|
||||
Nash in the 2023-2024 Centerstage season.
|
||||
|
||||
## Installation
|
||||
The quickest way to get started is with the quickstart [here](https://github.com/AnyiLin/Pedro-Pathing-Quickstart).
|
||||
|
||||
Otherwise, take the `pedroPathing` folder and put it under the `teamcode` folder in your project.
|
||||
You can do this from either downloading the project from the above quickstart link or the 10158
|
||||
CENTERSTAGE repository [here](https://github.com/AnyiLin/10158-Centerstage).
|
||||
|
||||
For this version of Pedro Pathing, the localizer used is the Road Runner localizer. To install its
|
||||
dependencies:
|
||||
1. Find `build.dependencies.gradle` in the main folder of your project.
|
||||
2. Add the following code to the end of the `repositories` block:
|
||||
```
|
||||
maven { url = 'https://maven.brott.dev/' }
|
||||
```
|
||||
3. Then, add the following code to the end of your `dependencies` block:
|
||||
```
|
||||
implementation 'com.acmerobotics.dashboard:dashboard:0.4.5'
|
||||
```
|
||||
4. Find the `build.gradle` file under the `teamcode` folder.
|
||||
5. In this gradle file, add the following dependency:
|
||||
```
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.7'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.21'
|
||||
```
|
@ -0,0 +1,119 @@
|
||||
## Prerequisites
|
||||
Obviously, you have to have a robot to use Pedro Pathing. Also, Pedro Pathing is only able to work
|
||||
with omnidirectional drives, like mecanum drive. There is currently no support for swerve drives.
|
||||
You must also have a localizer of some sort. Pedro Pathing has a drive encoder, a two tracking wheel,
|
||||
and a three tracking wheel localizer. You will need to have your localizer tuned before starting to
|
||||
tune PedroPathing. Check out the tuning guide under the localization tab if you're planning on using one of the
|
||||
localizers available in Pedro Pathing. Additionally, using [FTC Dashboard](http://192.168.43.1:8080/dash)
|
||||
will help a lot in tuning. Team 16166 Watt'S Up made a path visualizer linked [here](https://pedro-path-generator.vercel.app).
|
||||
The old Desmos visualizer is [here](https://www.desmos.com/calculator/3so1zx0hcd), but the one by
|
||||
Watt'S Up is honestly a lot better.
|
||||
One last thing to note is that Pedro Pathing operates in inches and radians. You can use centimeters
|
||||
instead of inches, but you'll have to input all your measurement in centimeters, and any distances
|
||||
that the tuners require you to push the robot or the tuners output will say "inches" when the actual
|
||||
measurements will be in centimeters.
|
||||
|
||||
## Tuning
|
||||
* To start with, we need the mass of the robot in kg. This is used for the centripetal force correction,
|
||||
and the mass, with the variable name `mass`, should be put on line `86` in the `FollowerConstants`
|
||||
class under the `tuning` package.
|
||||
|
||||
* Next, we need to find the preferred mecanum drive vectors. The rollers on mecanum wheels point at a
|
||||
45 degree angle from the forward direction, but the actual direction the force is output is actually
|
||||
closer to forward. To find the direction your wheels will go, you will need to run the
|
||||
`Forward Velocity Tuner` and `Strafe Velocity Tuner` OpModes. These will run your robot at full
|
||||
power for 40 inches forward and to the right, respectively. The distance can be changed through FTC
|
||||
Dashboard under the dropdown for each respective class, but higher distances work better. After the
|
||||
distance has finished running, the end velocity will be output to telemetry. The robot may continue
|
||||
to drift a little bit after the robot has finished running the distance, so make sure you have
|
||||
plenty of room. Once you're done, put the velocity for the `Forward Velocity Tuner` on line `33` in
|
||||
the `FollowerConstants` class, and the velocity for the `Strafe Velocity Tuner` on line `34` in the
|
||||
`FollowerConstants` class. The variable names should be `xMovement` and `yMovement`, respectively.
|
||||
|
||||
* The last set of automatic tuners you'll need to run are the zero power acceleration tuners. These
|
||||
find the rate at which your robot decelerates when power is cut from the drivetrain. This is used to
|
||||
get a more accurate estimation of the drive vector. To find this, you will need to run the
|
||||
`Forward Zero Power Acceleration Tuner` and the `Lateral Zero Power Acceleration Tuner` OpModes.
|
||||
These will run your robot until it hits a velocity of 10 inches/second forward and to the right,
|
||||
respectively. The velocity can be changed through FTC Dashboard under the dropdown for each
|
||||
respective class, but higher velocities work better. After the velocity has been reached, power will
|
||||
be cut from the drivetrain and the robot's deceleration will be tracked until the robot stops, at
|
||||
which point it will display the deceleration in telemetry. This robot will need to drift to a stop
|
||||
to properly work, and the higher the velocity the greater the drift distance, so make sure you have
|
||||
enough room. Once you're done, put the zero power acceleration for the
|
||||
`Forward Zero Power Acceleration Tuner` on line `94` in the `FollowerConstants` class and the zero
|
||||
power acceleration for the `Lateral Zero Power Acceleration Tuner` on line `98` in the
|
||||
`FollowerConstants` class. The variable names should be `forwardZeroPowerAcceleration` and
|
||||
`lateralZeroPowerAcceleration`, respectively.
|
||||
|
||||
* After this, we will want to tune the translational PID. Go to FTC Dashboard and disable all but
|
||||
the `useTranslational` checkboxes under the `Follower` tab. Then, run `StraightBackAndForth`. Make
|
||||
sure you disable the timer on autonomous OpModes. The PID for the translational error is called
|
||||
`translationalPIDF`. If you need to add a feedforward value, use the `translationalPIDFFeedForward`
|
||||
since that will add the feedforward in the direction the robot is trying to move, rather than the
|
||||
feedforward in the PIDF itself, since those will only add the feedforward one way. You can change
|
||||
the PIDF constants and feedforward values, under the `FollowerConstants` tab in FTC Dashboard.
|
||||
To tune the PID, push the robot off the path and see how corrects. You will want to alternate sides
|
||||
you push to reduce field wear and tear as well as push with varying power and distance. I would
|
||||
recommend tuning the PID so that it is capable of correcting while minimizing oscillations and still
|
||||
achieving a satisfactory level of accuracy. Overall, try to tune for fewer oscillations rather than
|
||||
higher speeds or perfect accuracy, since this will make the robot run more smoothly under actual
|
||||
pathing conditions.
|
||||
|
||||
* Next, we will tune the heading PID. The process is essentially the same as above, except you will
|
||||
want to only enable `useHeading` under `Follower` on FTC Dashboard, as well as turn the robot from
|
||||
opposing corners instead of pushing the robot. Naturally, instead of changing the stuff with
|
||||
"translational" in the name, you will instead want to look for stuff with "heading" in the name.
|
||||
Otherwise, these two PIDs are functionally very similar. The same tips from above will apply to this.
|
||||
|
||||
* Afterwards, we will tune the drive PID. Before we continue, we will need to set the
|
||||
`zeroPowerAccelerationMultiplier`. This determines how fast your robot will decelerate as a factor
|
||||
of how fast your robot will coast to a stop. Honestly, this is up to you. I personally used 4, but
|
||||
what works best for you is most important. Higher numbers will cause a faster brake, but increase
|
||||
oscillations at the end. Lower numbers will do the opposite. This can be found on line `107` in
|
||||
`FollowerConstants`, named `zeroPowerAccelerationMultiplier`. The drive PID is much, much more sensitive than the others. For reference,
|
||||
my P values were in the hundredths and thousandths place values, and my D values were in the hundred
|
||||
thousandths and millionths place values. To tune this, enable `useDrive`, `useHeading`, and
|
||||
`useTranslational` in the `Follower` dropdown in FTC Dashboard. Next, run `StraightBackAndForth`
|
||||
and don't forget to turn off the timer on the OpMode. Then, tune the PID following the tips from
|
||||
earlier. For this, it is very important to try to reduce oscillations. Additionally, I would
|
||||
absolutely not recommend using the I, or integral, part of the PID for this. Using integral in
|
||||
drivetrain PIDs is already not ideal, but it will continuously build up error in this PID, causing
|
||||
major issues when it gets too strong. Don't use I; P and D are enough. In the versions of Pedro Pathing
|
||||
from after late July 2024, there is a Kalman filter on the drive error and the drive PID has a
|
||||
filter as well. These smooth out the drive error and PID response so that there is not as much
|
||||
oscillation during the braking portion of each path. The Kalman filter is tuned to have 6 for the
|
||||
model covariance and 1 for the data covariance. These values should work, but if you feel inclined
|
||||
to tune the Kalman filter yourself, a higher ratio of model covariance to data covariance means that
|
||||
the filter will rely more on its previous output rather than the data, and the opposite ratio will
|
||||
mean that the filter will rely more so on the data input (the raw drive error) rather than the model.
|
||||
The filtered PID functions like a normal PID, except the derivative term is a weighted average of the
|
||||
current derivative and the previous derivative. Currently, the weighting, or time constant for the
|
||||
drive filtered PID is 0.6, so the derivative output is 0.6 times the previous derivative plus 0.4
|
||||
times the current derivative. Feel free to mess around with these values and find what works best
|
||||
for your robot!
|
||||
|
||||
* Finally, we will want to tune the centripetal force correction. This is a pretty simple tune. Open
|
||||
up FTC Dashboard and enable everything under the `Follower` tab. Then, run `CurvedBackAndForth`
|
||||
and turn off its timer. If you notice the robot is correcting towards the inside of the curve
|
||||
as/after running a path, then increase `centripetalScaling`, which can be found on line `89` of
|
||||
`FollowerConstants`. If the robot is correcting towards the outside of the curve, then decrease
|
||||
`centripetalScaling`.
|
||||
|
||||
* Once you've found satisfactory tunings for everything, run the robot around in
|
||||
`StraightBackAndForth`, `CurvedBackAndForth`, or some paths of your own making. There's also
|
||||
`Circle`, but that's more so for fun than anything else. If you notice something could be improved,
|
||||
feel free to mess around more with your PIDs. That should be all! If you have any more questions,
|
||||
refer to the OVERVIEW readme file or the README readme file. Best of luck to your team this season! :)
|
||||
|
||||
## Note About the PIDs
|
||||
In versions of Pedro Pathing before early August 2024, there were 2 PIDs used in the translational,
|
||||
heading, and drive control. However, now there is only one main PID. The old system can still be used.
|
||||
Scroll down to the bottom of `FollowerConstants` and set all the booleans from lines `157` to `159`
|
||||
to true. They should be named `useSecondaryTranslationalPID`, `useSecondaryHeadingPID`, and `useSecondaryDrivePID`.
|
||||
This will enable the two PID system that Pedro Pathing originally used. From there, scroll
|
||||
down and all the values pertaining to the secondary PIDs will be there. The two PID system works with
|
||||
a PID that handles larger errors (the main PID) and a second PID to handle smaller errors (the
|
||||
secondary PID). The main PID should be tuned to move the error within the secondary PID's range
|
||||
without providing too much momentum that could cause an overshoot. The secondary PID should be tuned
|
||||
to correct within its range quickly and accurately while minimizing oscillations.
|
@ -0,0 +1,62 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.examples;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
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 org.firstinspires.ftc.teamcode.pedroPathing.follower.Follower;
|
||||
|
||||
/**
|
||||
* This is the TeleOpEnhancements OpMode. It is an example usage of the TeleOp enhancements that
|
||||
* Pedro Pathing is capable of.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/21/2024
|
||||
*/
|
||||
@TeleOp(name = "Pedro Pathing TeleOp Enhancements", group = "Test")
|
||||
public class TeleOpEnhancements extends OpMode {
|
||||
private Follower follower;
|
||||
|
||||
private DcMotorEx leftFront;
|
||||
private DcMotorEx leftRear;
|
||||
private DcMotorEx rightFront;
|
||||
private DcMotorEx rightRear;
|
||||
|
||||
/**
|
||||
* This initializes the drive motors as well as the Follower and motion Vectors.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
follower = new Follower(hardwareMap);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, leftFrontMotorName);
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, leftRearMotorName);
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, rightRearMotorName);
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, rightFrontMotorName);
|
||||
|
||||
leftFront.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
leftRear.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
rightRear.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
rightFront.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
|
||||
follower.startTeleopDrive();
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode. This is only drive control with Pedro Pathing live centripetal force
|
||||
* correction.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
follower.setTeleOpMovementVectors(-gamepad1.left_stick_y, -gamepad1.left_stick_x, -gamepad1.right_stick_x);
|
||||
follower.update();
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.follower;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
/**
|
||||
* This is the DriveVectorScaler class. This class takes in inputs Vectors for driving, heading
|
||||
* correction, and translational/centripetal correction and returns an array with wheel powers.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/4/2024
|
||||
*/
|
||||
public class DriveVectorScaler {
|
||||
// This is ordered left front, left back, right front, right back. These are also normalized.
|
||||
private Vector[] mecanumVectors;
|
||||
|
||||
/**
|
||||
* This creates a new DriveVectorScaler, which takes in various movement vectors and outputs
|
||||
* the wheel drive powers necessary to move in the intended direction, given the true movement
|
||||
* vector for the front left mecanum wheel.
|
||||
*
|
||||
* @param frontLeftVector this is the front left mecanum wheel's preferred drive vector.
|
||||
*/
|
||||
public DriveVectorScaler(Vector frontLeftVector) {
|
||||
Vector copiedFrontLeftVector = MathFunctions.normalizeVector(frontLeftVector);
|
||||
mecanumVectors = new Vector[]{
|
||||
new Vector(copiedFrontLeftVector.getMagnitude(), copiedFrontLeftVector.getTheta()),
|
||||
new Vector(copiedFrontLeftVector.getMagnitude(), 2*Math.PI-copiedFrontLeftVector.getTheta()),
|
||||
new Vector(copiedFrontLeftVector.getMagnitude(), 2*Math.PI-copiedFrontLeftVector.getTheta()),
|
||||
new Vector(copiedFrontLeftVector.getMagnitude(), copiedFrontLeftVector.getTheta())};
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes in vectors for corrective power, heading power, and pathing power and outputs
|
||||
* an Array of four doubles, one for each wheel's motor power.
|
||||
*
|
||||
* IMPORTANT NOTE: all vector inputs are clamped between 0 and 1 inclusive in magnitude.
|
||||
*
|
||||
* @param correctivePower this Vector includes the centrifugal force scaling Vector as well as a
|
||||
* translational power Vector to correct onto the Bezier curve the Follower
|
||||
* is following.
|
||||
* @param headingPower this Vector points in the direction of the robot's current heaing, and
|
||||
* the magnitude tells the robot how much it should turn and in which
|
||||
* direction.
|
||||
* @param pathingPower this Vector points in the direction the robot needs to go to continue along
|
||||
* the Path.
|
||||
* @param robotHeading this is the current heading of the robot, which is used to calculate how
|
||||
* much power to allocate to each wheel.
|
||||
* @return this returns an Array of doubles with a length of 4, which contains the wheel powers.
|
||||
*/
|
||||
public double[] getDrivePowers(Vector correctivePower, Vector headingPower, Vector pathingPower, double robotHeading) {
|
||||
// clamps down the magnitudes of the input vectors
|
||||
if (correctivePower.getMagnitude() > 1) correctivePower.setMagnitude(1);
|
||||
if (headingPower.getMagnitude() > 1) headingPower.setMagnitude(1);
|
||||
if (pathingPower.getMagnitude() > 1) pathingPower.setMagnitude(1);
|
||||
|
||||
// the powers for the wheel vectors
|
||||
double [] wheelPowers = new double[4];
|
||||
|
||||
// This contains a copy of the mecanum wheel vectors
|
||||
Vector[] mecanumVectorsCopy = new Vector[4];
|
||||
|
||||
// this contains the pathing vectors, one for each side (heading control requires 2)
|
||||
Vector[] truePathingVectors = new Vector[2];
|
||||
|
||||
if (correctivePower.getMagnitude() == 1) {
|
||||
// checks for corrective power equal to 1 in magnitude. if equal to one, then set pathing power to that
|
||||
truePathingVectors[0] = MathFunctions.copyVector(correctivePower);
|
||||
truePathingVectors[1] = MathFunctions.copyVector(correctivePower);
|
||||
} else {
|
||||
// corrective power did not take up all the power, so add on heading power
|
||||
Vector leftSideVector = MathFunctions.subtractVectors(correctivePower, headingPower);
|
||||
Vector rightSideVector = MathFunctions.addVectors(correctivePower, headingPower);
|
||||
|
||||
if (leftSideVector.getMagnitude() > 1 || rightSideVector.getMagnitude() > 1) {
|
||||
//if the combined corrective and heading power is greater than 1, then scale down heading power
|
||||
double headingScalingFactor = Math.min(findNormalizingScaling(correctivePower, headingPower), findNormalizingScaling(correctivePower, MathFunctions.scalarMultiplyVector(headingPower, -1)));
|
||||
truePathingVectors[0] = MathFunctions.subtractVectors(correctivePower, MathFunctions.scalarMultiplyVector(headingPower, headingScalingFactor));
|
||||
truePathingVectors[1] = MathFunctions.addVectors(correctivePower, MathFunctions.scalarMultiplyVector(headingPower, headingScalingFactor));
|
||||
} else {
|
||||
// if we're here then we can add on some drive power but scaled down to 1
|
||||
Vector leftSideVectorWithPathing = MathFunctions.addVectors(leftSideVector, pathingPower);
|
||||
Vector rightSideVectorWithPathing = MathFunctions.addVectors(rightSideVector, pathingPower);
|
||||
|
||||
if (leftSideVectorWithPathing.getMagnitude() > 1 || rightSideVectorWithPathing.getMagnitude() > 1) {
|
||||
// too much power now, so we scale down the pathing vector
|
||||
double pathingScalingFactor = Math.min(findNormalizingScaling(leftSideVector, pathingPower), findNormalizingScaling(rightSideVector, pathingPower));
|
||||
truePathingVectors[0] = MathFunctions.addVectors(leftSideVector, MathFunctions.scalarMultiplyVector(pathingPower, pathingScalingFactor));
|
||||
truePathingVectors[1] = MathFunctions.addVectors(rightSideVector, MathFunctions.scalarMultiplyVector(pathingPower, pathingScalingFactor));
|
||||
} else {
|
||||
// just add the vectors together and you get the final vector
|
||||
truePathingVectors[0] = MathFunctions.copyVector(leftSideVectorWithPathing);
|
||||
truePathingVectors[1] = MathFunctions.copyVector(rightSideVectorWithPathing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
truePathingVectors[0] = MathFunctions.scalarMultiplyVector(truePathingVectors[0], 2.0);
|
||||
truePathingVectors[1] = MathFunctions.scalarMultiplyVector(truePathingVectors[1], 2.0);
|
||||
|
||||
for (int i = 0; i < mecanumVectorsCopy.length; i++) {
|
||||
// this copies the vectors from mecanumVectors but creates new references for them
|
||||
mecanumVectorsCopy[i] = MathFunctions.copyVector(mecanumVectors[i]);
|
||||
|
||||
mecanumVectorsCopy[i].rotateVector(robotHeading);
|
||||
}
|
||||
|
||||
wheelPowers[0] = (mecanumVectorsCopy[1].getXComponent()*truePathingVectors[0].getYComponent() - truePathingVectors[0].getXComponent()*mecanumVectorsCopy[1].getYComponent()) / (mecanumVectorsCopy[1].getXComponent()*mecanumVectorsCopy[0].getYComponent() - mecanumVectorsCopy[0].getXComponent()*mecanumVectorsCopy[1].getYComponent());
|
||||
wheelPowers[1] = (mecanumVectorsCopy[0].getXComponent()*truePathingVectors[0].getYComponent() - truePathingVectors[0].getXComponent()*mecanumVectorsCopy[0].getYComponent()) / (mecanumVectorsCopy[0].getXComponent()*mecanumVectorsCopy[1].getYComponent() - mecanumVectorsCopy[1].getXComponent()*mecanumVectorsCopy[0].getYComponent());
|
||||
wheelPowers[2] = (mecanumVectorsCopy[3].getXComponent()*truePathingVectors[1].getYComponent() - truePathingVectors[1].getXComponent()*mecanumVectorsCopy[3].getYComponent()) / (mecanumVectorsCopy[3].getXComponent()*mecanumVectorsCopy[2].getYComponent() - mecanumVectorsCopy[2].getXComponent()*mecanumVectorsCopy[3].getYComponent());
|
||||
wheelPowers[3] = (mecanumVectorsCopy[2].getXComponent()*truePathingVectors[1].getYComponent() - truePathingVectors[1].getXComponent()*mecanumVectorsCopy[2].getYComponent()) / (mecanumVectorsCopy[2].getXComponent()*mecanumVectorsCopy[3].getYComponent() - mecanumVectorsCopy[3].getXComponent()*mecanumVectorsCopy[2].getYComponent());
|
||||
|
||||
double wheelPowerMax = Math.max(Math.max(Math.abs(wheelPowers[0]), Math.abs(wheelPowers[1])), Math.max(Math.abs(wheelPowers[2]), Math.abs(wheelPowers[3])));
|
||||
if (wheelPowerMax > 1) {
|
||||
wheelPowers[0] /= wheelPowerMax;
|
||||
wheelPowers[1] /= wheelPowerMax;
|
||||
wheelPowers[2] /= wheelPowerMax;
|
||||
wheelPowers[3] /= wheelPowerMax;
|
||||
}
|
||||
|
||||
return wheelPowers;
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes in two Vectors, one static and one variable, and returns the scaling factor that,
|
||||
* when multiplied to the variable Vector, results in magnitude of the sum of the static Vector
|
||||
* and the scaled variable Vector being 1.
|
||||
*
|
||||
* IMPORTANT NOTE: I did not intend for this to be used for anything other than the method above
|
||||
* this one in this class, so there will be errors if you input Vectors of length greater than 1,
|
||||
* and it will scale up the variable Vector if the magnitude of the sum of the two input Vectors
|
||||
* isn't greater than 1. So, just don't use this elsewhere. There's gotta be a better way to do
|
||||
* whatever you're trying to do.
|
||||
*
|
||||
* I know that this is used outside of this class, however, I created this method so I get to
|
||||
* use it if I want to. Also, it's only used once outside of the DriveVectorScaler class, and
|
||||
* it's used to scale Vectors, as intended.
|
||||
*
|
||||
* @param staticVector the Vector that is held constant.
|
||||
* @param variableVector the Vector getting scaled to make the sum of the input Vectors have a
|
||||
* magnitude of 1.
|
||||
* @return returns the scaling factor for the variable Vector.
|
||||
*/
|
||||
public double findNormalizingScaling(Vector staticVector, Vector variableVector) {
|
||||
double a = Math.pow(variableVector.getXComponent(), 2) + Math.pow(variableVector.getYComponent(), 2);
|
||||
double b = staticVector.getXComponent() * variableVector.getXComponent() + staticVector.getYComponent() * variableVector.getYComponent();
|
||||
double c = Math.pow(staticVector.getXComponent(), 2) + Math.pow(staticVector.getYComponent(), 2) - 1.0;
|
||||
return (-b + Math.sqrt(Math.pow(b, 2) - a*c))/(a);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,83 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
|
||||
/**
|
||||
* This is the Encoder class. This tracks the position of a motor of class DcMotorEx. The motor
|
||||
* must have an encoder attached. It can also get changes in position.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
public class Encoder {
|
||||
private DcMotorEx motor;
|
||||
private double previousPosition;
|
||||
private double currentPosition;
|
||||
private double multiplier;
|
||||
|
||||
public final static double FORWARD = 1, REVERSE = -1;
|
||||
|
||||
/**
|
||||
* This creates a new Encoder from a DcMotorEx.
|
||||
*
|
||||
* @param setMotor the motor this will be tracking
|
||||
*/
|
||||
public Encoder(DcMotorEx setMotor) {
|
||||
motor = setMotor;
|
||||
multiplier = FORWARD;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the direction/multiplier of the Encoder. Setting 1 or -1 will make the Encoder track
|
||||
* forward or in reverse, respectively. Any multiple of either one will scale the Encoder's output
|
||||
* by that amount.
|
||||
*
|
||||
* @param setMultiplier the multiplier/direction to set
|
||||
*/
|
||||
public void setDirection(double setMultiplier) {
|
||||
multiplier = setMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the Encoder's position and the current and previous position in the code.
|
||||
*/
|
||||
public void reset() {
|
||||
motor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
|
||||
previousPosition = motor.getCurrentPosition();
|
||||
currentPosition = motor.getCurrentPosition();
|
||||
motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the Encoder's tracked current position and previous position.
|
||||
*/
|
||||
public void update() {
|
||||
previousPosition = currentPosition;
|
||||
currentPosition = motor.getCurrentPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier/direction of the Encoder.
|
||||
*
|
||||
* @return returns the multiplier
|
||||
*/
|
||||
public double getMultiplier() {
|
||||
return multiplier * (motor.getDirection() == DcMotorSimple.Direction.FORWARD ? 1 : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the change in position from the previous position to the current position. One
|
||||
* important thing to note is that this encoder does not track velocity, only change in position.
|
||||
* This is because I am using a pose exponential method of localization, which doesn't need the
|
||||
* velocity of the encoders. Velocity of the robot is calculated in the localizer using an elapsed
|
||||
* time timer there.
|
||||
*
|
||||
* @return returns the change in position of the Encoder
|
||||
*/
|
||||
public double getDeltaPosition() {
|
||||
return getMultiplier() * (currentPosition - previousPosition);
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
/**
|
||||
* This is the Localizer class. It is an abstract superclass of all localizers used in Pedro Pathing,
|
||||
* so it contains abstract methods that will have a concrete implementation in the subclasses. Any
|
||||
* method that all localizers will need will be in this class.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
public abstract class Localizer {
|
||||
|
||||
/**
|
||||
* This returns the current pose estimate from the Localizer.
|
||||
*
|
||||
* @return returns the pose as a Pose object.
|
||||
*/
|
||||
public abstract Pose getPose();
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate from the Localizer.
|
||||
*
|
||||
* @return returns the velocity as a Pose object.
|
||||
*/
|
||||
public abstract Pose getVelocity();
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate from the Localizer as a Vector.
|
||||
*
|
||||
* @return returns the velocity as a Vector.
|
||||
*/
|
||||
public abstract Vector getVelocityVector();
|
||||
|
||||
/**
|
||||
* This sets the start pose of the Localizer. Changing the start pose should move the robot as if
|
||||
* all its previous movements were displacing it from its new start pose.
|
||||
*
|
||||
* @param setStart the new start pose
|
||||
*/
|
||||
public abstract void setStartPose(Pose setStart);
|
||||
|
||||
/**
|
||||
* This sets the current pose estimate of the Localizer. Changing this should just change the
|
||||
* robot's current pose estimate, not anything to do with the start pose.
|
||||
*
|
||||
* @param setPose the new current pose estimate
|
||||
*/
|
||||
public abstract void setPose(Pose setPose);
|
||||
|
||||
/**
|
||||
* This calls an update to the Localizer, updating the current pose estimate and current velocity
|
||||
* estimate.
|
||||
*/
|
||||
public abstract void update();
|
||||
|
||||
/**
|
||||
* This returns how far the robot has turned in radians, in a number not clamped between 0 and
|
||||
* 2 * pi radians. This is used for some tuning things and nothing actually within the following.
|
||||
*
|
||||
* @return returns how far the robot has turned in total, in radians.
|
||||
*/
|
||||
public abstract double getTotalHeading();
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to forward movement measurement to convert from encoder
|
||||
* ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the forward ticks to inches multiplier
|
||||
*/
|
||||
public abstract double getForwardMultiplier();
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to lateral/strafe movement measurement to convert from
|
||||
* encoder ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the lateral/strafe ticks to inches multiplier
|
||||
*/
|
||||
public abstract double getLateralMultiplier();
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to turning movement measurement to convert from encoder
|
||||
* ticks to radians. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the turning ticks to radians multiplier
|
||||
*/
|
||||
public abstract double getTurningMultiplier();
|
||||
|
||||
/**
|
||||
* This resets the IMU of the localizer, if applicable.
|
||||
*/
|
||||
public abstract void resetIMU();
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This is the Matrix class. This defines matrices, primarily for use in the localizers. However, if
|
||||
* matrices and matrix operations are necessary, this class as well as some operations in the
|
||||
* MathFunctions class can absolutely be used there as well. It's similar to Mats in OpenCV if you've
|
||||
* used them before, but with more limited functionality.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
public class Matrix {
|
||||
private double[][] matrix;
|
||||
|
||||
/**
|
||||
* This creates a new Matrix of width and height 0.
|
||||
*/
|
||||
public Matrix() {
|
||||
matrix = new double[0][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Matrix with a specified width and height.
|
||||
*
|
||||
* @param rows the number of rows, or height
|
||||
* @param columns the number of columns, or width
|
||||
*/
|
||||
public Matrix(int rows, int columns) {
|
||||
matrix = new double[rows][columns];
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Matrix from a 2D matrix of doubles. Please only enter rectangular 2D
|
||||
* Arrays of doubles or else things mess up.
|
||||
*
|
||||
* @param setMatrix the 2D Array of doubles
|
||||
*/
|
||||
public Matrix(double[][] setMatrix) {
|
||||
setMatrix(setMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Matrix from another Matrix.
|
||||
*
|
||||
* @param setMatrix the Matrix input.
|
||||
*/
|
||||
public Matrix(Matrix setMatrix) {
|
||||
setMatrix(setMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a copy of a 2D Array of doubles that references entirely new memory locations
|
||||
* from the original 2D Array of doubles, so no issues with mutability.
|
||||
*
|
||||
* @param copyMatrix the 2D Array of doubles to copy
|
||||
* @return returns a deep copy of the input Array
|
||||
*/
|
||||
public static double[][] deepCopy(double[][] copyMatrix) {
|
||||
double[][] returnMatrix = new double[copyMatrix.length][copyMatrix[0].length];
|
||||
for (int i = 0; i < copyMatrix.length; i++) {
|
||||
returnMatrix[i] = Arrays.copyOf(copyMatrix[i], copyMatrix[i].length);
|
||||
}
|
||||
return returnMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a deep copy of the 2D Array that this Matrix is based on.
|
||||
*
|
||||
* @return returns the 2D Array of doubles this Matrix is built on
|
||||
*/
|
||||
public double[][] getMatrix() {
|
||||
return deepCopy(matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a specified row of the Matrix in the form of an Array of doubles.
|
||||
*
|
||||
* @param row the index of the row to return
|
||||
* @return returns the row of the Matrix specified
|
||||
*/
|
||||
public double[] get(int row) {
|
||||
return Arrays.copyOf(matrix[row], matrix[row].length);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a specified element of the Matrix as a double.
|
||||
*
|
||||
* @param row the index of the row of the element
|
||||
* @param column the index of the column of the element
|
||||
* @return returns the element of the Matrix specified
|
||||
*/
|
||||
public double get(int row, int column) {
|
||||
return get(row)[column];
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the number of rows of the Matrix, as determined by the length of the 2D Array.
|
||||
* If the Matrix/2D Array is not rectangular, issues arise.
|
||||
*
|
||||
* @return returns the number of rows in the Matrix
|
||||
*/
|
||||
public int getRows() {
|
||||
return matrix.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the number of columns of the Matrix, as determined by the length of the first Array
|
||||
* in the 2D Array. If the Matrix/2D Array is not rectangular, issues arise.
|
||||
*
|
||||
* @return returns the number of columns in the Matrix
|
||||
*/
|
||||
public int getColumns() {
|
||||
return matrix[0].length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the 2D Array of this Matrix to a copy of the 2D Array of another Matrix.
|
||||
*
|
||||
* @param setMatrix the Matrix to copy from
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean setMatrix(Matrix setMatrix) {
|
||||
return setMatrix(setMatrix.getMatrix());
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the 2D Array of this Matrix to a copy of a specified 2D Array.
|
||||
*
|
||||
* @param setMatrix the 2D Array to copy from
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean setMatrix(double[][] setMatrix) {
|
||||
int columns = setMatrix[0].length;
|
||||
for (int i = 0; i < setMatrix.length; i++) {
|
||||
if (setMatrix[i].length != columns) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
matrix = deepCopy(setMatrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a row of the Matrix to a copy of a specified Array of doubles.
|
||||
*
|
||||
* @param row the row to be written over
|
||||
* @param input the Array input
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean set(int row, double[] input) {
|
||||
if (input.length != getColumns()) {
|
||||
return false;
|
||||
}
|
||||
matrix[row] = Arrays.copyOf(input, input.length);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a specified element of the Matrix to an input value.
|
||||
*
|
||||
* @param row the index of the row of the specified element
|
||||
* @param column the index of the column of the specified element
|
||||
* @param input the input value
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean set(int row, int column, double input) {
|
||||
matrix[row][column] = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds a Matrix to this Matrix.
|
||||
*
|
||||
* @param input the Matrix to add to this. Nothing will change in this Matrix
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean add(Matrix input) {
|
||||
if (input.getRows() == getRows() && input.getColumns() == getColumns()) {
|
||||
for (int i = 0; i < getRows(); i++) {
|
||||
for (int j = 0; j < getColumns(); j++) {
|
||||
set(i, j, get(i,j) + input.get(i,j));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This subtracts a Matrix from this Matrix.
|
||||
*
|
||||
* @param input the Matrix to subtract from this. Nothing will change in this Matrix
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean subtract(Matrix input) {
|
||||
if (input.getRows() == getRows() && input.getColumns() == getColumns()) {
|
||||
for (int i = 0; i < getRows(); i++) {
|
||||
for (int j = 0; j < getColumns(); j++) {
|
||||
set(i, j, get(i,j) - input.get(i,j));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies this Matrix with a scalar.
|
||||
*
|
||||
* @param scalar the scalar number
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean scalarMultiply(double scalar) {
|
||||
for (int i = 0; i < getRows(); i++) {
|
||||
for (int j = 0; j < getColumns(); j++) {
|
||||
set(i, j, scalar * get(i,j));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies the Matrix by -1, flipping the signs of all the elements within.
|
||||
*
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean flipSigns() {
|
||||
return scalarMultiply(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies a Matrix to this Matrix.
|
||||
*
|
||||
* @param input the Matrix to multiply to this. Nothing will change in this Matrix
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public boolean multiply(Matrix input) {
|
||||
if (getColumns() == input.getRows()) {
|
||||
Matrix product = new Matrix(getRows(), input.getColumns());
|
||||
for (int i = 0; i < product.getRows(); i++) {
|
||||
for (int j = 0; j < product.getColumns(); j++) {
|
||||
double value = 0;
|
||||
for (int k = 0; k < get(i).length; k++) {
|
||||
value += get(i, k) * input.get(k, j);
|
||||
}
|
||||
product.set(i, j, value);
|
||||
}
|
||||
}
|
||||
setMatrix(product);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies a Matrix to another Matrix. This will not change any data in the two input
|
||||
* Matrices.
|
||||
*
|
||||
* @param one the first Matrix to multiply.
|
||||
* @param two the second Matrix to multiply
|
||||
* @return returns if the operation was successful
|
||||
*/
|
||||
public static Matrix multiply(Matrix one, Matrix two) {
|
||||
Matrix returnMatrix = new Matrix(one);
|
||||
if (returnMatrix.multiply(two)) {
|
||||
return returnMatrix;
|
||||
} else {
|
||||
return new Matrix();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
/**
|
||||
* This is the Pose class. It defines poses in 2D space, like the Pose2D class in Road Runner except
|
||||
* in the Pedro Pathing code so I don't have to import the Road Runner library. A Pose consists of
|
||||
* two coordinates defining a position and a third value for the heading, so basically just defining
|
||||
* any position and orientation the robot can be at, unless your robot can fly for whatever reason.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
public class Pose {
|
||||
private double x;
|
||||
private double y;
|
||||
private double heading;
|
||||
|
||||
/**
|
||||
* This creates a new Pose from a x, y, and heading inputs.
|
||||
*
|
||||
* @param setX the initial x value
|
||||
* @param setY the initial y value
|
||||
* @param setHeading the initial heading value
|
||||
*/
|
||||
public Pose(double setX, double setY, double setHeading) {
|
||||
setX(setX);
|
||||
setY(setY);
|
||||
setHeading(setHeading);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Pose from x and y inputs. The heading is set to 0.
|
||||
*
|
||||
* @param setX the initial x value
|
||||
* @param setY the initial y value
|
||||
*/
|
||||
public Pose(double setX, double setY) {
|
||||
this(setX, setY, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Pose with no inputs and 0 for all values.
|
||||
*/
|
||||
public Pose() {
|
||||
this(0,0,0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the x value.
|
||||
*
|
||||
* @param set the x value
|
||||
*/
|
||||
public void setX(double set) {
|
||||
x = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the y value.
|
||||
*
|
||||
* @param set the y value
|
||||
*/
|
||||
public void setY(double set) {
|
||||
y = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the heading value.
|
||||
*
|
||||
* @param set the heading value
|
||||
*/
|
||||
public void setHeading(double set) {
|
||||
heading = MathFunctions.normalizeAngle(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the x value.
|
||||
*
|
||||
* @return returns the x value
|
||||
*/
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the y value.
|
||||
*
|
||||
* @return returns the y value
|
||||
*/
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the heading value.
|
||||
*
|
||||
* @return returns the heading value
|
||||
*/
|
||||
public double getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the Pose as a Vector. Naturally, the heading data in the Pose cannot be included
|
||||
* in the Vector.
|
||||
*
|
||||
* @return returns the Pose as a Vector
|
||||
*/
|
||||
public Vector getVector() {
|
||||
Vector returnVector = new Vector();
|
||||
returnVector.setOrthogonalComponents(x, y);
|
||||
return returnVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a new Vector with magnitude 1 pointing in the direction of the heading.
|
||||
*
|
||||
* @return returns a unit Vector in the direction of the heading
|
||||
*/
|
||||
public Vector getHeadingVector() {
|
||||
return new Vector(1, heading);
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds all the values of an input Pose to this Pose. The input Pose's data will not be
|
||||
* changed.
|
||||
*
|
||||
* @param pose the input Pose
|
||||
*/
|
||||
public void add(Pose pose) {
|
||||
setX(x + pose.getX());
|
||||
setY(y + pose.getY());
|
||||
setHeading(heading + pose.getHeading());
|
||||
}
|
||||
|
||||
/**
|
||||
* This subtracts all the values of an input Pose from this Pose. The input Pose's data will not
|
||||
* be changed.
|
||||
*
|
||||
* @param pose the input Pose
|
||||
*/
|
||||
public void subtract(Pose pose) {
|
||||
setX(x - pose.getX());
|
||||
setY(y - pose.getY());
|
||||
setHeading(heading - pose.getHeading());
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies all the values of this Pose by a scalar.
|
||||
*
|
||||
* @param scalar the scalar
|
||||
*/
|
||||
public void scalarMultiply(double scalar) {
|
||||
setX(x * scalar);
|
||||
setY(y * scalar);
|
||||
setHeading(heading * scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* This divides all the values of this Pose by a scalar.
|
||||
*
|
||||
* @param scalar the scalar
|
||||
*/
|
||||
public void scalarDivide(double scalar) {
|
||||
setX(x / scalar);
|
||||
setY(y / scalar);
|
||||
setHeading(heading / scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* This flips the signs of all values in this Pose by multiplying them by -1. Heading values are
|
||||
* still normalized to be between 0 and 2 * pi in value.
|
||||
*/
|
||||
public void flipSigns() {
|
||||
setX(-x);
|
||||
setY(-y);
|
||||
setHeading(-heading);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns if a Pose is within a specified accuracy of this Pose in terms of x position,
|
||||
* y position, and heading.
|
||||
*
|
||||
* @param pose the input Pose to check
|
||||
* @param accuracy the specified accuracy necessary to return true
|
||||
* @return returns if the input Pose is within the specified accuracy of this Pose
|
||||
*/
|
||||
public boolean roughlyEquals(Pose pose, double accuracy) {
|
||||
return MathFunctions.roughlyEquals(x, pose.getX(), accuracy) && MathFunctions.roughlyEquals(y, pose.getY(), accuracy) && MathFunctions.roughlyEquals(MathFunctions.getSmallestAngleDifference(heading, pose.getHeading()), 0, accuracy);
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the input Pose is within 0.0001 in all values to this Pose.
|
||||
*
|
||||
* @param pose the input Pose
|
||||
* @return returns if the input Pose is within 0.0001 of this Pose
|
||||
*/
|
||||
public boolean roughlyEquals(Pose pose) {
|
||||
return roughlyEquals(pose, 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a copy of this Pose that points to a new memory location.
|
||||
*
|
||||
* @return returns a deep copy of this Pose
|
||||
*/
|
||||
public Pose copy() {
|
||||
return new Pose(getX(), getY(), getHeading());
|
||||
}
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
|
||||
import com.qualcomm.hardware.lynx.LynxModule;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import com.qualcomm.robotcore.hardware.IMU;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers.ThreeWheelIMULocalizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers.ThreeWheelLocalizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers.TwoWheelLocalizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
/**
|
||||
* This is the PoseUpdater class. This class handles getting pose data from the localizer and returning
|
||||
* the information in a useful way to the Follower.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/4/2024
|
||||
*/
|
||||
public class PoseUpdater {
|
||||
private HardwareMap hardwareMap;
|
||||
|
||||
private IMU imu;
|
||||
|
||||
private Localizer localizer;
|
||||
|
||||
private Pose startingPose = new Pose(0,0,0);
|
||||
|
||||
private Pose currentPose = startingPose;
|
||||
|
||||
private Pose previousPose = startingPose;
|
||||
|
||||
private Vector currentVelocity = new Vector();
|
||||
|
||||
private Vector previousVelocity = new Vector();
|
||||
|
||||
private Vector currentAcceleration = new Vector();
|
||||
|
||||
private double xOffset = 0;
|
||||
private double yOffset = 0;
|
||||
private double headingOffset = 0;
|
||||
|
||||
private long previousPoseTime;
|
||||
private long currentPoseTime;
|
||||
|
||||
/**
|
||||
* Creates a new PoseUpdater from a HardwareMap and a Localizer.
|
||||
*
|
||||
* @param hardwareMap the HardwareMap
|
||||
* @param localizer the Localizer
|
||||
*/
|
||||
public PoseUpdater(HardwareMap hardwareMap, Localizer localizer) {
|
||||
this.hardwareMap = hardwareMap;
|
||||
|
||||
for (LynxModule module : hardwareMap.getAll(LynxModule.class)) {
|
||||
module.setBulkCachingMode(LynxModule.BulkCachingMode.AUTO);
|
||||
}
|
||||
|
||||
this.localizer = localizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PoseUpdater from a HardwareMap.
|
||||
*
|
||||
* @param hardwareMap the HardwareMap
|
||||
*/
|
||||
public PoseUpdater(HardwareMap hardwareMap) {
|
||||
// TODO: replace the second argument with your preferred localizer
|
||||
this(hardwareMap, new ThreeWheelIMULocalizer(hardwareMap));
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the robot's pose, as well as updating the previous pose, velocity, and
|
||||
* acceleration. The cache for the current pose, velocity, and acceleration is cleared, and
|
||||
* the time stamps are updated as well.
|
||||
*/
|
||||
public void update() {
|
||||
previousVelocity = getVelocity();
|
||||
previousPose = applyOffset(getRawPose());
|
||||
currentPose = null;
|
||||
currentVelocity = null;
|
||||
currentAcceleration = null;
|
||||
previousPoseTime = currentPoseTime;
|
||||
currentPoseTime = System.nanoTime();
|
||||
localizer.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the starting pose. Do not run this after moving at all.
|
||||
*
|
||||
* @param set the Pose to set the starting pose to.
|
||||
*/
|
||||
public void setStartingPose(Pose set) {
|
||||
startingPose = set;
|
||||
previousPose = startingPose;
|
||||
previousPoseTime = System.nanoTime();
|
||||
currentPoseTime = System.nanoTime();
|
||||
localizer.setStartPose(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose, using offsets. Think of using offsets as setting trim in an
|
||||
* aircraft. This can be reset as well, so beware of using the resetOffset() method.
|
||||
*
|
||||
*
|
||||
* @param set The pose to set the current pose to.
|
||||
*/
|
||||
public void setCurrentPoseWithOffset(Pose set) {
|
||||
Pose currentPose = getRawPose();
|
||||
setXOffset(set.getX() - currentPose.getX());
|
||||
setYOffset(set.getY() - currentPose.getY());
|
||||
setHeadingOffset(MathFunctions.getTurnDirection(currentPose.getHeading(), set.getHeading()) * MathFunctions.getSmallestAngleDifference(currentPose.getHeading(), set.getHeading()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the offset for only the x position.
|
||||
*
|
||||
* @param offset This sets the offset.
|
||||
*/
|
||||
public void setXOffset(double offset) {
|
||||
xOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the offset for only the y position.
|
||||
*
|
||||
* @param offset This sets the offset.
|
||||
*/
|
||||
public void setYOffset(double offset) {
|
||||
yOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the offset for only the heading.
|
||||
*
|
||||
* @param offset This sets the offset.
|
||||
*/
|
||||
public void setHeadingOffset(double offset) {
|
||||
headingOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the x offset.
|
||||
*
|
||||
* @return returns the x offset.
|
||||
*/
|
||||
public double getXOffset() {
|
||||
return xOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the y offset.
|
||||
*
|
||||
* @return returns the y offset.
|
||||
*/
|
||||
public double getYOffset() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the heading offset.
|
||||
*
|
||||
* @return returns the heading offset.
|
||||
*/
|
||||
public double getHeadingOffset() {
|
||||
return headingOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the offset to a specified Pose.
|
||||
*
|
||||
* @param pose The pose to be offset.
|
||||
* @return This returns a new Pose with the offset applied.
|
||||
*/
|
||||
public Pose applyOffset(Pose pose) {
|
||||
return new Pose(pose.getX()+xOffset, pose.getY()+yOffset, pose.getHeading()+headingOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets all offsets set to the PoseUpdater. If you have reset your pose using the
|
||||
* setCurrentPoseUsingOffset(Pose2d set) method, then your pose will be returned to what the
|
||||
* PoseUpdater thinks your pose would be, not the pose you reset to.
|
||||
*/
|
||||
public void resetOffset() {
|
||||
setXOffset(0);
|
||||
setYOffset(0);
|
||||
setHeadingOffset(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current pose, with offsets applied. If this is called multiple times in
|
||||
* a single update, the current pose is cached so that subsequent calls don't have to repeat
|
||||
* localizer calls or calculations.
|
||||
*
|
||||
* @return returns the current pose.
|
||||
*/
|
||||
public Pose getPose() {
|
||||
if (currentPose == null) {
|
||||
currentPose = localizer.getPose();
|
||||
return applyOffset(currentPose);
|
||||
} else {
|
||||
return applyOffset(currentPose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current raw pose, without any offsets applied. If this is called multiple times in
|
||||
* a single update, the current pose is cached so that subsequent calls don't have to repeat
|
||||
* localizer calls or calculations.
|
||||
*
|
||||
* @return returns the raw pose.
|
||||
*/
|
||||
public Pose getRawPose() {
|
||||
if (currentPose == null) {
|
||||
currentPose = localizer.getPose();
|
||||
return currentPose;
|
||||
} else {
|
||||
return currentPose;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose without using resettable offsets.
|
||||
*
|
||||
* @param set the pose to set the current pose to.
|
||||
*/
|
||||
public void setPose(Pose set) {
|
||||
resetOffset();
|
||||
localizer.setPose(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the robot's pose from the previous update.
|
||||
*
|
||||
* @return returns the robot's previous pose.
|
||||
*/
|
||||
public Pose getPreviousPose() {
|
||||
return previousPose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the robot's change in pose from the previous update.
|
||||
*
|
||||
* @return returns the robot's delta pose.
|
||||
*/
|
||||
public Pose getDeltaPose() {
|
||||
Pose returnPose = getPose();
|
||||
returnPose.subtract(previousPose);
|
||||
return returnPose;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the velocity of the robot as a Vector. If this is called multiple times in
|
||||
* a single update, the velocity Vector is cached so that subsequent calls don't have to repeat
|
||||
* localizer calls or calculations.
|
||||
*
|
||||
* @return returns the velocity of the robot.
|
||||
*/
|
||||
public Vector getVelocity() {
|
||||
if (currentVelocity == null) {
|
||||
currentVelocity = new Vector();
|
||||
currentVelocity.setOrthogonalComponents(getPose().getX() - previousPose.getX(), getPose().getY() - previousPose.getY());
|
||||
currentVelocity.setMagnitude(MathFunctions.distance(getPose(), previousPose) / ((currentPoseTime - previousPoseTime) / Math.pow(10.0, 9)));
|
||||
return MathFunctions.copyVector(currentVelocity);
|
||||
} else {
|
||||
return MathFunctions.copyVector(currentVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the angular velocity of the robot as a double.
|
||||
*
|
||||
* @return returns the angular velocity of the robot.
|
||||
*/
|
||||
public double getAngularVelocity() {
|
||||
return MathFunctions.getTurnDirection(previousPose.getHeading(), getPose().getHeading()) * MathFunctions.getSmallestAngleDifference(getPose().getHeading(), previousPose.getHeading()) / ((currentPoseTime-previousPoseTime)/Math.pow(10.0, 9));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the acceleration of the robot as a Vector. If this is called multiple times in
|
||||
* a single update, the acceleration Vector is cached so that subsequent calls don't have to
|
||||
* repeat localizer calls or calculations.
|
||||
*
|
||||
* @return returns the acceleration of the robot.
|
||||
*/
|
||||
public Vector getAcceleration() {
|
||||
if (currentAcceleration == null) {
|
||||
currentAcceleration = MathFunctions.subtractVectors(getVelocity(), previousVelocity);
|
||||
currentAcceleration.setMagnitude(currentAcceleration.getMagnitude() / ((currentPoseTime - previousPoseTime) / Math.pow(10.0, 9)));
|
||||
return MathFunctions.copyVector(currentAcceleration);
|
||||
} else {
|
||||
return MathFunctions.copyVector(currentAcceleration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the heading of the robot to the IMU's heading, using Road Runner's pose reset.
|
||||
*/
|
||||
public void resetHeadingToIMU() {
|
||||
localizer.setPose(new Pose(getPose().getX(), getPose().getY(), getNormalizedIMUHeading() + startingPose.getHeading()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the heading of the robot to the IMU's heading, using offsets instead of Road
|
||||
* Runner's pose reset. This way, it's faster, but this can be wiped with the resetOffsets()
|
||||
* method.
|
||||
*/
|
||||
public void resetHeadingToIMUWithOffsets() {
|
||||
setCurrentPoseWithOffset(new Pose(getPose().getX(), getPose().getY(), getNormalizedIMUHeading() + startingPose.getHeading()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the IMU heading normalized to be between [0, 2 PI] radians.
|
||||
*
|
||||
* @return returns the normalized IMU heading.
|
||||
*/
|
||||
public double getNormalizedIMUHeading() {
|
||||
return MathFunctions.normalizeAngle(-imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the total number of radians the robot has turned.
|
||||
*
|
||||
* @return the total heading.
|
||||
*/
|
||||
public double getTotalHeading() {
|
||||
return localizer.getTotalHeading();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the Localizer.
|
||||
*
|
||||
* @return the Localizer
|
||||
*/
|
||||
public Localizer getLocalizer() {
|
||||
return localizer;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void resetIMU() {
|
||||
localizer.resetIMU();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.acmerobotics.roadrunner.ftc
|
||||
|
||||
import com.qualcomm.hardware.sparkfun.SparkFunOTOS
|
||||
import com.qualcomm.robotcore.hardware.I2cDeviceSynch
|
||||
import com.qualcomm.robotcore.hardware.configuration.annotations.DeviceProperties
|
||||
import com.qualcomm.robotcore.hardware.configuration.annotations.I2cDeviceType
|
||||
import java.util.*
|
||||
@I2cDeviceType
|
||||
@DeviceProperties(
|
||||
name = "SparkFun OTOS Corrected",
|
||||
xmlTag = "SparkFunOTOS2",
|
||||
description = "SparkFun Qwiic Optical Tracking Odometry Sensor Corrected"
|
||||
)
|
||||
class SparkFunOTOSCorrected(deviceClient: I2cDeviceSynch) : SparkFunOTOS(deviceClient) {
|
||||
/**
|
||||
* Gets only the position and velocity measured by the
|
||||
* OTOS in a single burst read
|
||||
* @param pos Position measured by the OTOS
|
||||
* @param vel Velocity measured by the OTOS
|
||||
*/
|
||||
fun getPosVel(pos: Pose2D, vel: Pose2D) {
|
||||
// Read all pose registers
|
||||
val rawData = deviceClient.read(REG_POS_XL.toInt(), 12)
|
||||
|
||||
// Convert raw data to pose units
|
||||
pos.set(regsToPose(Arrays.copyOfRange(rawData, 0, 6), INT16_TO_METER, INT16_TO_RAD))
|
||||
vel.set(regsToPose(Arrays.copyOfRange(rawData, 6, 12), INT16_TO_MPS, INT16_TO_RPS))
|
||||
}
|
||||
|
||||
// Modified version of poseToRegs to fix pose setting issue
|
||||
// see https://discord.com/channels/225450307654647808/1246977443030368349/1271702497659977760
|
||||
override fun poseToRegs(rawData: ByteArray, pose: Pose2D, xyToRaw: Double, hToRaw: Double) {
|
||||
// Convert pose units to raw data
|
||||
val rawX = (_distanceUnit.toMeters(pose.x) * xyToRaw).toInt().toShort()
|
||||
val rawY = (_distanceUnit.toMeters(pose.y) * xyToRaw).toInt().toShort()
|
||||
val rawH = (_angularUnit.toRadians(pose.h) * hToRaw).toInt().toShort()
|
||||
|
||||
// Store raw data in buffer
|
||||
rawData[0] = (rawX.toInt() and 0xFF).toByte()
|
||||
rawData[1] = ((rawX.toInt() shr 8) and 0xFF).toByte()
|
||||
rawData[2] = (rawY.toInt() and 0xFF).toByte()
|
||||
rawData[3] = ((rawY.toInt() shr 8) and 0xFF).toByte()
|
||||
rawData[4] = (rawH.toInt() and 0xFF).toByte()
|
||||
rawData[5] = ((rawH.toInt() shr 8) and 0xFF).toByte()
|
||||
}
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Encoder;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Localizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Matrix;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.NanoTimer;
|
||||
|
||||
/**
|
||||
* This is the DriveEncoderLocalizer class. This class extends the Localizer superclass and is a
|
||||
* localizer that uses the drive encoder set up.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
@Config
|
||||
public class DriveEncoderLocalizer extends Localizer {
|
||||
private HardwareMap hardwareMap;
|
||||
private Pose startPose;
|
||||
private Pose displacementPose;
|
||||
private Pose currentVelocity;
|
||||
private Matrix prevRotationMatrix;
|
||||
private NanoTimer timer;
|
||||
private long deltaTimeNano;
|
||||
private Encoder leftFront;
|
||||
private Encoder rightFront;
|
||||
private Encoder leftRear;
|
||||
private Encoder rightRear;
|
||||
private double totalHeading;
|
||||
public static double FORWARD_TICKS_TO_INCHES = 1;
|
||||
public static double STRAFE_TICKS_TO_INCHES = 1;
|
||||
public static double TURN_TICKS_TO_RADIANS = 1;
|
||||
public static double ROBOT_WIDTH = 1;
|
||||
public static double ROBOT_LENGTH = 1;
|
||||
|
||||
/**
|
||||
* This creates a new DriveEncoderLocalizer from a HardwareMap, with a starting Pose at (0,0)
|
||||
* facing 0 heading.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
*/
|
||||
public DriveEncoderLocalizer(HardwareMap map) {
|
||||
this(map, new Pose());
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new DriveEncoderLocalizer from a HardwareMap and a Pose, with the Pose
|
||||
* specifying the starting pose of the localizer.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
* @param setStartPose the Pose to start from
|
||||
*/
|
||||
public DriveEncoderLocalizer(HardwareMap map, Pose setStartPose) {
|
||||
hardwareMap = map;
|
||||
|
||||
leftFront = new Encoder(hardwareMap.get(DcMotorEx.class, leftFrontMotorName));
|
||||
leftRear = new Encoder(hardwareMap.get(DcMotorEx.class, leftRearMotorName));
|
||||
rightRear = new Encoder(hardwareMap.get(DcMotorEx.class, rightRearMotorName));
|
||||
rightFront = new Encoder(hardwareMap.get(DcMotorEx.class, rightFrontMotorName));
|
||||
|
||||
// TODO: reverse any encoders necessary
|
||||
leftFront.setDirection(Encoder.REVERSE);
|
||||
rightRear.setDirection(Encoder.REVERSE);
|
||||
leftRear.setDirection(Encoder.FORWARD);
|
||||
rightRear.setDirection(Encoder.FORWARD);
|
||||
|
||||
setStartPose(setStartPose);
|
||||
timer = new NanoTimer();
|
||||
deltaTimeNano = 1;
|
||||
displacementPose = new Pose();
|
||||
currentVelocity = new Pose();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current pose estimate.
|
||||
*
|
||||
* @return returns the current pose estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getPose() {
|
||||
return MathFunctions.addPoses(startPose, displacementPose);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getVelocity() {
|
||||
return currentVelocity.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Vector
|
||||
*/
|
||||
@Override
|
||||
public Vector getVelocityVector() {
|
||||
return currentVelocity.getVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the start pose. Changing the start pose should move the robot as if all its
|
||||
* previous movements were displacing it from its new start pose.
|
||||
*
|
||||
* @param setStart the new start pose
|
||||
*/
|
||||
@Override
|
||||
public void setStartPose(Pose setStart) {
|
||||
startPose = setStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the Matrix that contains the previous pose's heading rotation.
|
||||
*
|
||||
* @param heading the rotation of the Matrix
|
||||
*/
|
||||
public void setPrevRotationMatrix(double heading) {
|
||||
prevRotationMatrix = new Matrix(3,3);
|
||||
prevRotationMatrix.set(0, 0, Math.cos(heading));
|
||||
prevRotationMatrix.set(0, 1, -Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 0, Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 1, Math.cos(heading));
|
||||
prevRotationMatrix.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose estimate. Changing this should just change the robot's current
|
||||
* pose estimate, not anything to do with the start pose.
|
||||
*
|
||||
* @param setPose the new current pose estimate
|
||||
*/
|
||||
@Override
|
||||
public void setPose(Pose setPose) {
|
||||
displacementPose = MathFunctions.subtractPoses(setPose, startPose);
|
||||
resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the elapsed time timer that keeps track of time between updates, as well as the
|
||||
* change position of the Encoders. Then, the robot's global change in position is calculated
|
||||
* using the pose exponential method.
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
deltaTimeNano = timer.getElapsedTime();
|
||||
timer.resetTimer();
|
||||
|
||||
updateEncoders();
|
||||
Matrix robotDeltas = getRobotDeltas();
|
||||
Matrix globalDeltas;
|
||||
setPrevRotationMatrix(getPose().getHeading());
|
||||
|
||||
Matrix transformation = new Matrix(3,3);
|
||||
if (Math.abs(robotDeltas.get(2, 0)) < 0.001) {
|
||||
transformation.set(0, 0, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(0, 1, -robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 0, robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 1, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
} else {
|
||||
transformation.set(0, 0, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(0, 1, (Math.cos(robotDeltas.get(2, 0)) - 1.0) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 0, (1.0 - Math.cos(robotDeltas.get(2, 0))) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 1, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
globalDeltas = Matrix.multiply(Matrix.multiply(prevRotationMatrix, transformation), robotDeltas);
|
||||
|
||||
displacementPose.add(new Pose(globalDeltas.get(0, 0), globalDeltas.get(1, 0), globalDeltas.get(2, 0)));
|
||||
currentVelocity = new Pose(globalDeltas.get(0, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(1, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(2, 0) / (deltaTimeNano * Math.pow(10.0, 9)));
|
||||
|
||||
totalHeading += globalDeltas.get(2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the Encoders.
|
||||
*/
|
||||
public void updateEncoders() {
|
||||
leftFront.update();
|
||||
rightFront.update();
|
||||
leftRear.update();
|
||||
rightRear.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the Encoders.
|
||||
*/
|
||||
public void resetEncoders() {
|
||||
leftFront.reset();
|
||||
rightFront.reset();
|
||||
leftRear.reset();
|
||||
rightRear.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This calculates the change in position from the perspective of the robot using information
|
||||
* from the Encoders.
|
||||
*
|
||||
* @return returns a Matrix containing the robot relative movement.
|
||||
*/
|
||||
public Matrix getRobotDeltas() {
|
||||
Matrix returnMatrix = new Matrix(3,1);
|
||||
// x/forward movement
|
||||
returnMatrix.set(0,0, FORWARD_TICKS_TO_INCHES * (leftFront.getDeltaPosition() + rightFront.getDeltaPosition() + leftRear.getDeltaPosition() + rightRear.getDeltaPosition()));
|
||||
//y/strafe movement
|
||||
returnMatrix.set(1,0, STRAFE_TICKS_TO_INCHES * (-leftFront.getDeltaPosition() + rightFront.getDeltaPosition() + leftRear.getDeltaPosition() - rightRear.getDeltaPosition()));
|
||||
// theta/turning
|
||||
returnMatrix.set(2,0, TURN_TICKS_TO_RADIANS * ((-leftFront.getDeltaPosition() + rightFront.getDeltaPosition() - leftRear.getDeltaPosition() + rightRear.getDeltaPosition()) / (ROBOT_WIDTH + ROBOT_LENGTH)));
|
||||
return returnMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns how far the robot has turned in radians, in a number not clamped between 0 and
|
||||
* 2 * pi radians. This is used for some tuning things and nothing actually within the following.
|
||||
*
|
||||
* @return returns how far the robot has turned in total, in radians.
|
||||
*/
|
||||
public double getTotalHeading() {
|
||||
return totalHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to forward movement measurement to convert from encoder
|
||||
* ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the forward ticks to inches multiplier
|
||||
*/
|
||||
public double getForwardMultiplier() {
|
||||
return FORWARD_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to lateral/strafe movement measurement to convert from
|
||||
* encoder ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the lateral/strafe ticks to inches multiplier
|
||||
*/
|
||||
public double getLateralMultiplier() {
|
||||
return STRAFE_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to turning movement measurement to convert from encoder
|
||||
* ticks to radians. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the turning ticks to radians multiplier
|
||||
*/
|
||||
public double getTurningMultiplier() {
|
||||
return TURN_TICKS_TO_RADIANS;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does nothing since this localizer does not use the IMU.
|
||||
*/
|
||||
public void resetIMU() {
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers;
|
||||
|
||||
import com.qualcomm.hardware.sparkfun.SparkFunOTOS;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Localizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
/**
|
||||
* This is the OTOSLocalizer class. This class extends the Localizer superclass and is a
|
||||
* localizer that uses the SparkFun OTOS. The diagram below, which is modified from
|
||||
* Road Runner, shows a typical set up.
|
||||
*
|
||||
* The view is from the top of the robot looking downwards.
|
||||
*
|
||||
* left on robot is the y positive direction
|
||||
*
|
||||
* forward on robot is the x positive direction
|
||||
*
|
||||
* /--------------\
|
||||
* | ____ |
|
||||
* | ---- |
|
||||
* | || || |
|
||||
* | || || | ----> left (y positive)
|
||||
* | |
|
||||
* | |
|
||||
* \--------------/
|
||||
* |
|
||||
* |
|
||||
* V
|
||||
* forward (x positive)
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 7/20/2024
|
||||
*/
|
||||
public class OTOSLocalizer extends Localizer {
|
||||
private HardwareMap hardwareMap;
|
||||
private Pose startPose;
|
||||
private SparkFunOTOS otos;
|
||||
private double previousHeading;
|
||||
private double totalHeading;
|
||||
|
||||
/**
|
||||
* This creates a new OTOSLocalizer from a HardwareMap, with a starting Pose at (0,0)
|
||||
* facing 0 heading.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
*/
|
||||
public OTOSLocalizer(HardwareMap map) {
|
||||
this(map, new Pose());
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new OTOSLocalizer from a HardwareMap and a Pose, with the Pose
|
||||
* specifying the starting pose of the localizer.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
* @param setStartPose the Pose to start from
|
||||
*/
|
||||
public OTOSLocalizer(HardwareMap map, Pose setStartPose) {
|
||||
hardwareMap = map;
|
||||
|
||||
// TODO: replace this with your OTOS port
|
||||
/*
|
||||
TODO: If you want to use the "SparkFunOTOSCorrected" version of OTOS, then replace the
|
||||
'SparkFunOTOS.class' below with 'SparkFunOTOSCorrected.class' and set the OTOS as a
|
||||
"SparkFunOTOS Corrected" in your robot confg
|
||||
*/
|
||||
SparkFunOTOS
|
||||
otos = hardwareMap.get(SparkFunOTOS.class, "sensor_otos");
|
||||
|
||||
otos.setLinearUnit(DistanceUnit.INCH);
|
||||
otos.setAngularUnit(AngleUnit.RADIANS);
|
||||
|
||||
// TODO: replace this with your OTOS offset from the center of the robot
|
||||
// For the OTOS, left/right is the y axis and forward/backward is the x axis, with left being
|
||||
// positive y and forward being positive x. PI/2 radians is facing forward, and clockwise
|
||||
// rotation is negative rotation.
|
||||
otos.setOffset(new SparkFunOTOS.Pose2D(0,0,Math.PI / 2));
|
||||
|
||||
// TODO: replace these with your tuned multipliers
|
||||
otos.setLinearScalar(1.0);
|
||||
otos.setAngularScalar(1.0);
|
||||
|
||||
otos.calibrateImu();
|
||||
otos.resetTracking();
|
||||
|
||||
setStartPose(setStartPose);
|
||||
totalHeading = 0;
|
||||
previousHeading = startPose.getHeading();
|
||||
|
||||
resetOTOS();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current pose estimate.
|
||||
*
|
||||
* @return returns the current pose estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getPose() {
|
||||
SparkFunOTOS.Pose2D pose = otos.getPosition();
|
||||
return MathFunctions.addPoses(startPose, new Pose(pose.x, pose.y, pose.h));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getVelocity() {
|
||||
SparkFunOTOS.Pose2D OTOSVelocity = otos.getVelocity();
|
||||
return new Pose(OTOSVelocity.x, OTOSVelocity.y, OTOSVelocity.h);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Vector
|
||||
*/
|
||||
@Override
|
||||
public Vector getVelocityVector() {
|
||||
return getVelocity().getVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the start pose. Changing the start pose should move the robot as if all its
|
||||
* previous movements were displacing it from its new start pose.
|
||||
*
|
||||
* @param setStart the new start pose
|
||||
*/
|
||||
@Override
|
||||
public void setStartPose(Pose setStart) {
|
||||
startPose = setStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose estimate. Changing this should just change the robot's current
|
||||
* pose estimate, not anything to do with the start pose.
|
||||
*
|
||||
* @param setPose the new current pose estimate
|
||||
*/
|
||||
@Override
|
||||
public void setPose(Pose setPose) {
|
||||
resetOTOS();
|
||||
Pose setOTOSPose = MathFunctions.subtractPoses(setPose, startPose);
|
||||
otos.setPosition(new SparkFunOTOS.Pose2D(setOTOSPose.getX(), setOTOSPose.getY(), setOTOSPose.getHeading()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the total heading of the robot. The OTOS handles all other updates itself.
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
totalHeading += MathFunctions.getSmallestAngleDifference(otos.getPosition().h, previousHeading);
|
||||
previousHeading = otos.getPosition().h;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the OTOS.
|
||||
*/
|
||||
public void resetOTOS() {
|
||||
otos.resetTracking();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns how far the robot has turned in radians, in a number not clamped between 0 and
|
||||
* 2 * pi radians. This is used for some tuning things and nothing actually within the following.
|
||||
*
|
||||
* @return returns how far the robot has turned in total, in radians.
|
||||
*/
|
||||
public double getTotalHeading() {
|
||||
return totalHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to forward movement measurement to convert from OTOS
|
||||
* ticks to inches. For the OTOS, this value is the same as the lateral multiplier.
|
||||
* This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the forward ticks to inches multiplier
|
||||
*/
|
||||
public double getForwardMultiplier() {
|
||||
return otos.getLinearScalar();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to lateral/strafe movement measurement to convert from
|
||||
* OTOS ticks to inches. For the OTOS, this value is the same as the forward multiplier.
|
||||
* This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the lateral/strafe ticks to inches multiplier
|
||||
*/
|
||||
public double getLateralMultiplier() {
|
||||
return otos.getLinearScalar();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to turning movement measurement to convert from OTOS ticks
|
||||
* to radians. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the turning ticks to radians multiplier
|
||||
*/
|
||||
public double getTurningMultiplier() {
|
||||
return otos.getAngularScalar();
|
||||
}
|
||||
|
||||
/**
|
||||
* This does nothing since this localizer does not use the IMU.
|
||||
*/
|
||||
public void resetIMU() {
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
//package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
//
|
||||
//import com.acmerobotics.roadrunner.geometry.Pose2d;
|
||||
//import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
//
|
||||
//import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
//import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
//
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.List;
|
||||
//
|
||||
///**
|
||||
// * This is the RRToPedroThreeWheelLocalizer class. This class extends the Localizer superclass and
|
||||
// * is intended to adapt the old Road Runner three wheel odometry localizer to the new Pedro Pathing
|
||||
// * localizer system.
|
||||
// *
|
||||
// * @author Anyi Lin - 10158 Scott's Bots
|
||||
// * @version 1.0, 5/9/2024
|
||||
// */
|
||||
//public class RRToPedroThreeWheelLocalizer extends Localizer {
|
||||
// private RoadRunnerThreeWheelLocalizer localizer;
|
||||
// private double totalHeading;
|
||||
// private Pose startPose;
|
||||
// private Pose previousPose;
|
||||
//
|
||||
// /**
|
||||
// * This creates a new RRToPedroThreeWheelLocalizer from a HardwareMap. This adapts the previously
|
||||
// * used Road Runner localization system to the new Pedro Pathing localization system.
|
||||
// *
|
||||
// * @param hardwareMap the HardwareMap
|
||||
// */
|
||||
// public RRToPedroThreeWheelLocalizer(HardwareMap hardwareMap) {
|
||||
// List<Integer> lastTrackingEncPositions = new ArrayList<>();
|
||||
// List<Integer> lastTrackingEncVels = new ArrayList<>();
|
||||
//
|
||||
// localizer = new RoadRunnerThreeWheelLocalizer(hardwareMap, lastTrackingEncPositions, lastTrackingEncVels);
|
||||
//
|
||||
// startPose = new Pose();
|
||||
// previousPose = new Pose();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns the current pose estimate as a Pose.
|
||||
// *
|
||||
// * @return returns the current pose estimate
|
||||
// */
|
||||
// @Override
|
||||
// public Pose getPose() {
|
||||
// Pose2d pose = localizer.getPoseEstimate();
|
||||
// return new Pose(pose.getX(), pose.getY(), pose.getHeading());
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns the current velocity estimate as a Pose.
|
||||
// *
|
||||
// * @return returns the current velocity estimate
|
||||
// */
|
||||
// @Override
|
||||
// public Pose getVelocity() {
|
||||
// Pose2d pose = localizer.getPoseVelocity();
|
||||
// return new Pose(pose.getX(), pose.getY(), pose.getHeading());
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns the current velocity estimate as a Vector.
|
||||
// *
|
||||
// * @return returns the current velocity estimate
|
||||
// */
|
||||
// @Override
|
||||
// public Vector getVelocityVector() {
|
||||
// Pose2d pose = localizer.getPoseVelocity();
|
||||
// Vector returnVector = new Vector();
|
||||
// returnVector.setOrthogonalComponents(pose.getX(), pose.getY());
|
||||
// return returnVector;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This sets the start pose. Any movement of the robot is treated as a displacement from the
|
||||
// * start pose, so moving the start pose will move the current pose estimate the same amount.
|
||||
// *
|
||||
// * @param setStart the new start pose
|
||||
// */
|
||||
// @Override
|
||||
// public void setStartPose(Pose setStart) {
|
||||
// Pose oldStart = startPose;
|
||||
// startPose = setStart;
|
||||
// Pose startDiff = MathFunctions.subtractPoses(startPose, oldStart);
|
||||
// localizer.setPoseEstimate(new Pose2d(getPose().getX() + startDiff.getX(), getPose().getY() + startDiff.getY(), getPose().getHeading() + startDiff.getHeading()));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This sets the current pose estimate. This has no effect on the start pose.
|
||||
// *
|
||||
// * @param setPose the new current pose estimate
|
||||
// */
|
||||
// @Override
|
||||
// public void setPose(Pose setPose) {
|
||||
// localizer.setPoseEstimate(new Pose2d(setPose.getX(), setPose.getY(), setPose.getHeading()));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This updates the total heading and previous pose estimate. Everything else is handled by the
|
||||
// * Road Runner localizer on its own, but updating this tells you how far the robot has really
|
||||
// * turned.
|
||||
// */
|
||||
// @Override
|
||||
// public void update() {
|
||||
// totalHeading += MathFunctions.getTurnDirection(previousPose.getHeading(), getPose().getHeading()) * MathFunctions.getSmallestAngleDifference(previousPose.getHeading(), getPose().getHeading());
|
||||
// previousPose = getPose();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns how far the robot has actually turned.
|
||||
// *
|
||||
// * @return returns the total angle turned, in degrees.
|
||||
// */
|
||||
// @Override
|
||||
// public double getTotalHeading() {
|
||||
// return totalHeading;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns the forward multiplier of the Road Runner localizer, which converts from ticks
|
||||
// * to inches. You can actually use the tuners in Pedro Pathing to find the value that everything
|
||||
// * multiplied together should be. If you do use that, then do be aware that the value returned is
|
||||
// * the product of the Road Runner ticks to inches and the x multiplier.
|
||||
// *
|
||||
// * @return returns the forward multiplier
|
||||
// */
|
||||
// @Override
|
||||
// public double getForwardMultiplier() {
|
||||
// return RoadRunnerThreeWheelLocalizer.encoderTicksToInches(1) * RoadRunnerThreeWheelLocalizer.X_MULTIPLIER;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns the lateral multiplier of the Road Runner localizer, which converts from ticks
|
||||
// * to inches. You can actually use the tuners in Pedro Pathing to find the value that everything
|
||||
// * multiplied together should be. If you do use that, then do be aware that the value returned is
|
||||
// * the product of the Road Runner ticks to inches and the y multiplier.
|
||||
// *
|
||||
// * @return returns the lateral multiplier
|
||||
// */
|
||||
// @Override
|
||||
// public double getLateralMultiplier() {
|
||||
// return RoadRunnerThreeWheelLocalizer.encoderTicksToInches(1) * RoadRunnerThreeWheelLocalizer.Y_MULTIPLIER;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This returns the turning multiplier of the Road Runner localizer, which doesn't actually exist.
|
||||
// * There really isn't a point in tuning the turning for the Road Runner localizer. This will
|
||||
// * actually just return the average of the two other multipliers.
|
||||
// *
|
||||
// * @return returns the turning multiplier
|
||||
// */
|
||||
// @Override
|
||||
// public double getTurningMultiplier() {
|
||||
// return (getForwardMultiplier() + getLateralMultiplier()) / 2;
|
||||
// }
|
||||
//}
|
@ -0,0 +1,132 @@
|
||||
//package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
//
|
||||
//import com.acmerobotics.roadrunner.util.NanoClock;
|
||||
//import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
//import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
//
|
||||
///**
|
||||
// * This class is adapted from the Road Runner Encoder class. Later, this will be replaced with a
|
||||
// * custom encoder class. According to Road Runner, this wraps a motor instance to provide corrected
|
||||
// * velocity counts and allow reversing independently of the corresponding slot's motor direction.
|
||||
// *
|
||||
// * I'm fairly sure I didn't make any changes to this class, just copied it so I wouldn't have to have
|
||||
// * import statements, so I'm not crediting myself as an author for this.
|
||||
// *
|
||||
// * @author Road Runner dev team
|
||||
// * @version 1.0, 5/9/2024
|
||||
// */
|
||||
//public class RoadRunnerEncoder {
|
||||
// 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 RoadRunnerEncoder(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 RoadRunnerEncoder(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,123 @@
|
||||
//package org.firstinspires.ftc.teamcode.pedroPathing.localization;
|
||||
//
|
||||
//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 java.util.Arrays;
|
||||
//import java.util.List;
|
||||
//
|
||||
///*
|
||||
// * Sample tracking wheel localizer implementation assuming the standard configuration:
|
||||
// *
|
||||
// * left on robot is y pos
|
||||
// *
|
||||
// * front of robot is x pos
|
||||
// *
|
||||
// * /--------------\
|
||||
// * | ____ |
|
||||
// * | ---- |
|
||||
// * | || || |
|
||||
// * | || || |
|
||||
// * | |
|
||||
// * | |
|
||||
// * \--------------/
|
||||
// *
|
||||
// */
|
||||
//
|
||||
///**
|
||||
// * This class is adapted from the Road Runner StandardTrackingWheelLocalizer class. Later, this will
|
||||
// * be replaced with a custom localizer. I made some minor changes, so I'm crediting myself as an
|
||||
// * 'author' of sorts, but really this is pretty much Road Runner's code, just moved to be local to
|
||||
// * Pedro Pathing to avoid having imports.
|
||||
// *
|
||||
// * @author Road Runner dev team
|
||||
// * @author Anyi Lin - 10158 Scott's Bots
|
||||
// * @version 1.0, 5/9/2024
|
||||
// */
|
||||
//@Config
|
||||
//public class RoadRunnerThreeWheelLocalizer extends ThreeTrackingWheelLocalizer {
|
||||
// public static double TICKS_PER_REV = 8192;
|
||||
// public static double WHEEL_RADIUS = 1.37795; // in
|
||||
// public static double GEAR_RATIO = 1; // output (wheel) speed / input (encoder) speed
|
||||
//
|
||||
// public static double X_MULTIPLIER = 0.5008239963;
|
||||
// public static double Y_MULTIPLIER = 0.5018874659;
|
||||
//
|
||||
// public static double leftX = -18.5/25.4 - 0.1, leftY = 164.4/25.4, rightX = -18.4/25.4 - 0.1, rightY = -159.6/25.4, strafeX = -107.9/25.4+0.25, strafeY = -1.1/25.4-0.23;
|
||||
//
|
||||
// private RoadRunnerEncoder leftEncoder, rightEncoder, strafeEncoder;
|
||||
//
|
||||
// private List<Integer> lastEncPositions, lastEncVels;
|
||||
//
|
||||
// public RoadRunnerThreeWheelLocalizer(HardwareMap hardwareMap, List<Integer> lastTrackingEncPositions, List<Integer> lastTrackingEncVels) {
|
||||
// super(Arrays.asList(
|
||||
// new Pose2d(leftX, leftY, 0), // left
|
||||
// new Pose2d(rightX, rightY, 0), // right
|
||||
// new Pose2d(strafeX, strafeY, Math.toRadians(90)) // strafe
|
||||
// ));
|
||||
//
|
||||
// lastEncPositions = lastTrackingEncPositions;
|
||||
// lastEncVels = lastTrackingEncVels;
|
||||
//
|
||||
// // TODO: redo the configs here
|
||||
// leftEncoder = new RoadRunnerEncoder(hardwareMap.get(DcMotorEx.class, "leftRear"));
|
||||
// rightEncoder = new RoadRunnerEncoder(hardwareMap.get(DcMotorEx.class, "rightFront"));
|
||||
// strafeEncoder = new RoadRunnerEncoder(hardwareMap.get(DcMotorEx.class, "strafeEncoder"));
|
||||
//
|
||||
// // TODO: reverse any encoders using Encoder.setDirection(Encoder.Direction.REVERSE)
|
||||
// leftEncoder.setDirection(RoadRunnerEncoder.Direction.REVERSE);
|
||||
// rightEncoder.setDirection(RoadRunnerEncoder.Direction.REVERSE);
|
||||
// strafeEncoder.setDirection(RoadRunnerEncoder.Direction.FORWARD);
|
||||
// }
|
||||
//
|
||||
// public void resetHeading(double heading) {
|
||||
// setPoseEstimate(new Pose2d(getPoseEstimate().getX(), getPoseEstimate().getY(), heading));
|
||||
// }
|
||||
//
|
||||
// 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 = strafeEncoder.getCurrentPosition();
|
||||
//
|
||||
// lastEncPositions.clear();
|
||||
// lastEncPositions.add(leftPos);
|
||||
// lastEncPositions.add(rightPos);
|
||||
// lastEncPositions.add(frontPos);
|
||||
//
|
||||
// return Arrays.asList(
|
||||
// encoderTicksToInches(leftPos) * X_MULTIPLIER,
|
||||
// encoderTicksToInches(rightPos) * X_MULTIPLIER,
|
||||
// encoderTicksToInches(frontPos) * Y_MULTIPLIER
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @NonNull
|
||||
// @Override
|
||||
// public List<Double> getWheelVelocities() {
|
||||
// int leftVel = (int) leftEncoder.getCorrectedVelocity();
|
||||
// int rightVel = (int) rightEncoder.getCorrectedVelocity();
|
||||
// int frontVel = (int) strafeEncoder.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,317 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.PedroConstants.*;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import com.qualcomm.robotcore.hardware.IMU;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Encoder;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Localizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Matrix;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.NanoTimer;
|
||||
|
||||
/**
|
||||
* This is the ThreeWheelIMULocalizer class. This class extends the Localizer superclass and is a
|
||||
* localizer that uses the three wheel odometry set up with the IMU to have more accurate heading
|
||||
* readings. The diagram below, which is modified from Road Runner, shows a typical set up.
|
||||
*
|
||||
* The view is from the top of the robot looking downwards.
|
||||
*
|
||||
* left on robot is the y positive direction
|
||||
*
|
||||
* forward on robot is the x positive direction
|
||||
*
|
||||
* /--------------\
|
||||
* | ____ |
|
||||
* | ---- |
|
||||
* | || || |
|
||||
* | || || | ----> left (y positive)
|
||||
* | |
|
||||
* | |
|
||||
* \--------------/
|
||||
* |
|
||||
* |
|
||||
* V
|
||||
* forward (x positive)
|
||||
*
|
||||
* @author Logan Nash
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 7/9/2024
|
||||
*/
|
||||
@Config
|
||||
public class ThreeWheelIMULocalizer extends Localizer {
|
||||
private HardwareMap hardwareMap;
|
||||
private Pose startPose;
|
||||
private Pose displacementPose;
|
||||
private Pose currentVelocity;
|
||||
private Matrix prevRotationMatrix;
|
||||
private NanoTimer timer;
|
||||
private long deltaTimeNano;
|
||||
private Encoder leftEncoder;
|
||||
private Encoder rightEncoder;
|
||||
private Encoder strafeEncoder;
|
||||
private Pose leftEncoderPose;
|
||||
private Pose rightEncoderPose;
|
||||
private Pose strafeEncoderPose;
|
||||
|
||||
public final IMU imu;
|
||||
private double previousIMUOrientation;
|
||||
private double deltaRadians;
|
||||
private double totalHeading;
|
||||
public static double FORWARD_TICKS_TO_INCHES = 0.004;//8192 * 1.37795 * 2 * Math.PI * 0.5008239963;
|
||||
public static double STRAFE_TICKS_TO_INCHES = -0.0036;//8192 * 1.37795 * 2 * Math.PI * 0.5018874659;
|
||||
public static double TURN_TICKS_TO_RADIANS = 0.0043;//8192 * 1.37795 * 2 * Math.PI * 0.5;
|
||||
|
||||
public static boolean useIMU = true;
|
||||
|
||||
/**
|
||||
* This creates a new ThreeWheelIMULocalizer from a HardwareMap, with a starting Pose at (0,0)
|
||||
* facing 0 heading.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
*/
|
||||
public ThreeWheelIMULocalizer(HardwareMap map) {
|
||||
this(map, new Pose());
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new ThreeWheelIMULocalizer from a HardwareMap and a Pose, with the Pose
|
||||
* specifying the starting pose of the localizer.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
* @param setStartPose the Pose to start from
|
||||
*/
|
||||
public ThreeWheelIMULocalizer(HardwareMap map, Pose setStartPose) {
|
||||
hardwareMap = map;
|
||||
imu = hardwareMap.get(IMU.class, IMU);
|
||||
|
||||
// TODO: replace this with your IMU's orientation
|
||||
imu.initialize(new IMU.Parameters(new RevHubOrientationOnRobot(IMU_LOGO_FACING_DIRECTION, IMU_USB_FACING_DIRECTION)));
|
||||
|
||||
// TODO: replace these with your encoder positions
|
||||
leftEncoderPose = new Pose(-7.625, 6.19375, 0);
|
||||
rightEncoderPose = new Pose(-7.625, -6.19375, 0);
|
||||
strafeEncoderPose = new Pose(7, 1, Math.toRadians(90));
|
||||
|
||||
// TODO: replace these with your encoder ports
|
||||
leftEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, LEFT_ENCODER));
|
||||
rightEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, RIGHT_ENCODER));
|
||||
strafeEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, BACK_ENCODER));
|
||||
|
||||
// TODO: reverse any encoders necessary
|
||||
leftEncoder.setDirection(LEFT_ENCODER_DIRECTION);
|
||||
rightEncoder.setDirection(RIGHT_ENCODER_DIRECTION);
|
||||
strafeEncoder.setDirection(BACK_ENCODER_DIRECTION);
|
||||
|
||||
setStartPose(setStartPose);
|
||||
timer = new NanoTimer();
|
||||
deltaTimeNano = 1;
|
||||
displacementPose = new Pose();
|
||||
currentVelocity = new Pose();
|
||||
totalHeading = 0;
|
||||
|
||||
resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current pose estimate.
|
||||
*
|
||||
* @return returns the current pose estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getPose() {
|
||||
return MathFunctions.addPoses(startPose, displacementPose);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getVelocity() {
|
||||
return currentVelocity.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Vector
|
||||
*/
|
||||
@Override
|
||||
public Vector getVelocityVector() {
|
||||
return currentVelocity.getVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the start pose. Changing the start pose should move the robot as if all its
|
||||
* previous movements were displacing it from its new start pose.
|
||||
*
|
||||
* @param setStart the new start pose
|
||||
*/
|
||||
@Override
|
||||
public void setStartPose(Pose setStart) {
|
||||
startPose = setStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the Matrix that contains the previous pose's heading rotation.
|
||||
*
|
||||
* @param heading the rotation of the Matrix
|
||||
*/
|
||||
public void setPrevRotationMatrix(double heading) {
|
||||
prevRotationMatrix = new Matrix(3,3);
|
||||
prevRotationMatrix.set(0, 0, Math.cos(heading));
|
||||
prevRotationMatrix.set(0, 1, -Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 0, Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 1, Math.cos(heading));
|
||||
prevRotationMatrix.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose estimate. Changing this should just change the robot's current
|
||||
* pose estimate, not anything to do with the start pose.
|
||||
*
|
||||
* @param setPose the new current pose estimate
|
||||
*/
|
||||
@Override
|
||||
public void setPose(Pose setPose) {
|
||||
displacementPose = MathFunctions.subtractPoses(setPose, startPose);
|
||||
resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the elapsed time timer that keeps track of time between updates, as well as the
|
||||
* change position of the Encoders. Then, the robot's global change in position is calculated
|
||||
* using the pose exponential method.
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
deltaTimeNano = timer.getElapsedTime();
|
||||
timer.resetTimer();
|
||||
|
||||
updateEncoders();
|
||||
Matrix robotDeltas = getRobotDeltas();
|
||||
Matrix globalDeltas;
|
||||
setPrevRotationMatrix(getPose().getHeading());
|
||||
|
||||
Matrix transformation = new Matrix(3,3);
|
||||
if (Math.abs(robotDeltas.get(2, 0)) < 0.001) {
|
||||
transformation.set(0, 0, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(0, 1, -robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 0, robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 1, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
} else {
|
||||
transformation.set(0, 0, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(0, 1, (Math.cos(robotDeltas.get(2, 0)) - 1.0) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 0, (1.0 - Math.cos(robotDeltas.get(2, 0))) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 1, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
globalDeltas = Matrix.multiply(Matrix.multiply(prevRotationMatrix, transformation), robotDeltas);
|
||||
|
||||
displacementPose.add(new Pose(globalDeltas.get(0, 0), globalDeltas.get(1, 0), globalDeltas.get(2, 0)));
|
||||
currentVelocity = new Pose(globalDeltas.get(0, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(1, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(2, 0) / (deltaTimeNano * Math.pow(10.0, 9)));
|
||||
|
||||
totalHeading += globalDeltas.get(2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the Encoders.
|
||||
*/
|
||||
public void updateEncoders() {
|
||||
leftEncoder.update();
|
||||
rightEncoder.update();
|
||||
strafeEncoder.update();
|
||||
|
||||
double currentIMUOrientation = MathFunctions.normalizeAngle(imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS));
|
||||
deltaRadians = MathFunctions.getTurnDirection(previousIMUOrientation, currentIMUOrientation) * MathFunctions.getSmallestAngleDifference(currentIMUOrientation, previousIMUOrientation);
|
||||
previousIMUOrientation = currentIMUOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the Encoders.
|
||||
*/
|
||||
public void resetEncoders() {
|
||||
leftEncoder.reset();
|
||||
rightEncoder.reset();
|
||||
strafeEncoder.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This calculates the change in position from the perspective of the robot using information
|
||||
* from the Encoders.
|
||||
*
|
||||
* @return returns a Matrix containing the robot relative movement.
|
||||
*/
|
||||
public Matrix getRobotDeltas() {
|
||||
Matrix returnMatrix = new Matrix(3,1);
|
||||
// x/forward movement
|
||||
returnMatrix.set(0,0, FORWARD_TICKS_TO_INCHES * ((rightEncoder.getDeltaPosition() * leftEncoderPose.getY() - leftEncoder.getDeltaPosition() * rightEncoderPose.getY()) / (leftEncoderPose.getY() - rightEncoderPose.getY())));
|
||||
//y/strafe movement
|
||||
returnMatrix.set(1,0, STRAFE_TICKS_TO_INCHES * (strafeEncoder.getDeltaPosition() - strafeEncoderPose.getX() * ((rightEncoder.getDeltaPosition() - leftEncoder.getDeltaPosition()) / (leftEncoderPose.getY() - rightEncoderPose.getY()))));
|
||||
// theta/turning
|
||||
if (MathFunctions.getSmallestAngleDifference(0, deltaRadians) > 0.00005 && useIMU) {
|
||||
returnMatrix.set(2, 0, deltaRadians);
|
||||
} else {
|
||||
returnMatrix.set(2,0, TURN_TICKS_TO_RADIANS * ((rightEncoder.getDeltaPosition() - leftEncoder.getDeltaPosition()) / (leftEncoderPose.getY() - rightEncoderPose.getY())));
|
||||
}
|
||||
return returnMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns how far the robot has turned in radians, in a number not clamped between 0 and
|
||||
* 2 * pi radians. This is used for some tuning things and nothing actually within the following.
|
||||
*
|
||||
* @return returns how far the robot has turned in total, in radians.
|
||||
*/
|
||||
public double getTotalHeading() {
|
||||
return totalHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to forward movement measurement to convert from encoder
|
||||
* ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the forward ticks to inches multiplier
|
||||
*/
|
||||
public double getForwardMultiplier() {
|
||||
return FORWARD_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to lateral/strafe movement measurement to convert from
|
||||
* encoder ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the lateral/strafe ticks to inches multiplier
|
||||
*/
|
||||
public double getLateralMultiplier() {
|
||||
return STRAFE_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to turning movement measurement to convert from encoder
|
||||
* ticks to radians. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the turning ticks to radians multiplier
|
||||
*/
|
||||
public double getTurningMultiplier() {
|
||||
return TURN_TICKS_TO_RADIANS;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the IMU.
|
||||
*/
|
||||
public void resetIMU() {
|
||||
imu.resetYaw();
|
||||
}
|
||||
}
|
@ -0,0 +1,294 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.PedroConstants.*;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Encoder;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Localizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Matrix;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.NanoTimer;
|
||||
|
||||
/**
|
||||
* This is the ThreeWheelLocalizer class. This class extends the Localizer superclass and is a
|
||||
* localizer that uses the three wheel odometry set up. The diagram below, which is modified from
|
||||
* Road Runner, shows a typical set up.
|
||||
*
|
||||
* The view is from the top of the robot looking downwards.
|
||||
*
|
||||
* left on robot is the y positive direction
|
||||
*
|
||||
* forward on robot is the x positive direction
|
||||
*
|
||||
* /--------------\
|
||||
* | ____ |
|
||||
* | ---- |
|
||||
* | || || |
|
||||
* | || || | ----> left (y positive)
|
||||
* | |
|
||||
* | |
|
||||
* \--------------/
|
||||
* |
|
||||
* |
|
||||
* V
|
||||
* forward (x positive)
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
@Config
|
||||
public class ThreeWheelLocalizer extends Localizer {
|
||||
private HardwareMap hardwareMap;
|
||||
private Pose startPose;
|
||||
private Pose displacementPose;
|
||||
private Pose currentVelocity;
|
||||
private Matrix prevRotationMatrix;
|
||||
private NanoTimer timer;
|
||||
private long deltaTimeNano;
|
||||
private Encoder leftEncoder;
|
||||
private Encoder rightEncoder;
|
||||
private Encoder strafeEncoder;
|
||||
private Pose leftEncoderPose;
|
||||
private Pose rightEncoderPose;
|
||||
private Pose strafeEncoderPose;
|
||||
private double totalHeading;
|
||||
public static double FORWARD_TICKS_TO_INCHES = 0.00052189;//8192 * 1.37795 * 2 * Math.PI * 0.5008239963;
|
||||
public static double STRAFE_TICKS_TO_INCHES = 0.00052189;//8192 * 1.37795 * 2 * Math.PI * 0.5018874659;
|
||||
public static double TURN_TICKS_TO_RADIANS = 0.00053717;//8192 * 1.37795 * 2 * Math.PI * 0.5;
|
||||
|
||||
/**
|
||||
* This creates a new ThreeWheelLocalizer from a HardwareMap, with a starting Pose at (0,0)
|
||||
* facing 0 heading.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
*/
|
||||
public ThreeWheelLocalizer(HardwareMap map) {
|
||||
this(map, new Pose());
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new ThreeWheelLocalizer from a HardwareMap and a Pose, with the Pose
|
||||
* specifying the starting pose of the localizer.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
* @param setStartPose the Pose to start from
|
||||
*/
|
||||
public ThreeWheelLocalizer(HardwareMap map, Pose setStartPose) {
|
||||
// TODO: replace these with your encoder positions
|
||||
leftEncoderPose = new Pose(-18.5/25.4 - 0.1, 164.4/25.4, 0);
|
||||
rightEncoderPose = new Pose(-18.4/25.4 - 0.1, -159.6/25.4, 0);
|
||||
strafeEncoderPose = new Pose(0*(-107.9/25.4+8)+-107.9/25.4+0.25, -1.1/25.4-0.23, Math.toRadians(90));
|
||||
|
||||
hardwareMap = map;
|
||||
|
||||
// TODO: replace these with your encoder ports
|
||||
leftEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, LEFT_ENCODER));
|
||||
rightEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, RIGHT_ENCODER));
|
||||
strafeEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, BACK_ENCODER));
|
||||
|
||||
// TODO: reverse any encoders necessary
|
||||
//leftEncoder.setDirection(Encoder.REVERSE);
|
||||
// rightEncoder.setDirection(Encoder.REVERSE);
|
||||
//strafeEncoder.setDirection(Encoder.FORWARD);
|
||||
|
||||
setStartPose(setStartPose);
|
||||
timer = new NanoTimer();
|
||||
deltaTimeNano = 1;
|
||||
displacementPose = new Pose();
|
||||
currentVelocity = new Pose();
|
||||
totalHeading = 0;
|
||||
|
||||
resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current pose estimate.
|
||||
*
|
||||
* @return returns the current pose estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getPose() {
|
||||
return MathFunctions.addPoses(startPose, displacementPose);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getVelocity() {
|
||||
return currentVelocity.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Vector
|
||||
*/
|
||||
@Override
|
||||
public Vector getVelocityVector() {
|
||||
return currentVelocity.getVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the start pose. Changing the start pose should move the robot as if all its
|
||||
* previous movements were displacing it from its new start pose.
|
||||
*
|
||||
* @param setStart the new start pose
|
||||
*/
|
||||
@Override
|
||||
public void setStartPose(Pose setStart) {
|
||||
startPose = setStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the Matrix that contains the previous pose's heading rotation.
|
||||
*
|
||||
* @param heading the rotation of the Matrix
|
||||
*/
|
||||
public void setPrevRotationMatrix(double heading) {
|
||||
prevRotationMatrix = new Matrix(3,3);
|
||||
prevRotationMatrix.set(0, 0, Math.cos(heading));
|
||||
prevRotationMatrix.set(0, 1, -Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 0, Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 1, Math.cos(heading));
|
||||
prevRotationMatrix.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose estimate. Changing this should just change the robot's current
|
||||
* pose estimate, not anything to do with the start pose.
|
||||
*
|
||||
* @param setPose the new current pose estimate
|
||||
*/
|
||||
@Override
|
||||
public void setPose(Pose setPose) {
|
||||
displacementPose = MathFunctions.subtractPoses(setPose, startPose);
|
||||
resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the elapsed time timer that keeps track of time between updates, as well as the
|
||||
* change position of the Encoders. Then, the robot's global change in position is calculated
|
||||
* using the pose exponential method.
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
deltaTimeNano = timer.getElapsedTime();
|
||||
timer.resetTimer();
|
||||
|
||||
updateEncoders();
|
||||
Matrix robotDeltas = getRobotDeltas();
|
||||
Matrix globalDeltas;
|
||||
setPrevRotationMatrix(getPose().getHeading());
|
||||
|
||||
Matrix transformation = new Matrix(3,3);
|
||||
if (Math.abs(robotDeltas.get(2, 0)) < 0.001) {
|
||||
transformation.set(0, 0, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(0, 1, -robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 0, robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 1, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
} else {
|
||||
transformation.set(0, 0, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(0, 1, (Math.cos(robotDeltas.get(2, 0)) - 1.0) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 0, (1.0 - Math.cos(robotDeltas.get(2, 0))) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 1, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
globalDeltas = Matrix.multiply(Matrix.multiply(prevRotationMatrix, transformation), robotDeltas);
|
||||
|
||||
displacementPose.add(new Pose(globalDeltas.get(0, 0), globalDeltas.get(1, 0), globalDeltas.get(2, 0)));
|
||||
currentVelocity = new Pose(globalDeltas.get(0, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(1, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(2, 0) / (deltaTimeNano * Math.pow(10.0, 9)));
|
||||
|
||||
totalHeading += globalDeltas.get(2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the Encoders.
|
||||
*/
|
||||
public void updateEncoders() {
|
||||
leftEncoder.update();
|
||||
rightEncoder.update();
|
||||
strafeEncoder.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the Encoders.
|
||||
*/
|
||||
public void resetEncoders() {
|
||||
leftEncoder.reset();
|
||||
rightEncoder.reset();
|
||||
strafeEncoder.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This calculates the change in position from the perspective of the robot using information
|
||||
* from the Encoders.
|
||||
*
|
||||
* @return returns a Matrix containing the robot relative movement.
|
||||
*/
|
||||
public Matrix getRobotDeltas() {
|
||||
Matrix returnMatrix = new Matrix(3,1);
|
||||
// x/forward movement
|
||||
returnMatrix.set(0,0, FORWARD_TICKS_TO_INCHES * ((rightEncoder.getDeltaPosition() * leftEncoderPose.getY() - leftEncoder.getDeltaPosition() * rightEncoderPose.getY()) / (leftEncoderPose.getY() - rightEncoderPose.getY())));
|
||||
//y/strafe movement
|
||||
returnMatrix.set(1,0, STRAFE_TICKS_TO_INCHES * (strafeEncoder.getDeltaPosition() - strafeEncoderPose.getX() * ((rightEncoder.getDeltaPosition() - leftEncoder.getDeltaPosition()) / (leftEncoderPose.getY() - rightEncoderPose.getY()))));
|
||||
// theta/turning
|
||||
returnMatrix.set(2,0, TURN_TICKS_TO_RADIANS * ((rightEncoder.getDeltaPosition() - leftEncoder.getDeltaPosition()) / (leftEncoderPose.getY() - rightEncoderPose.getY())));
|
||||
return returnMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns how far the robot has turned in radians, in a number not clamped between 0 and
|
||||
* 2 * pi radians. This is used for some tuning things and nothing actually within the following.
|
||||
*
|
||||
* @return returns how far the robot has turned in total, in radians.
|
||||
*/
|
||||
public double getTotalHeading() {
|
||||
return totalHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to forward movement measurement to convert from encoder
|
||||
* ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the forward ticks to inches multiplier
|
||||
*/
|
||||
public double getForwardMultiplier() {
|
||||
return FORWARD_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to lateral/strafe movement measurement to convert from
|
||||
* encoder ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the lateral/strafe ticks to inches multiplier
|
||||
*/
|
||||
public double getLateralMultiplier() {
|
||||
return STRAFE_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to turning movement measurement to convert from encoder
|
||||
* ticks to radians. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the turning ticks to radians multiplier
|
||||
*/
|
||||
public double getTurningMultiplier() {
|
||||
return TURN_TICKS_TO_RADIANS;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does nothing since this localizer does not use the IMU.
|
||||
*/
|
||||
public void resetIMU() {
|
||||
}
|
||||
}
|
@ -0,0 +1,302 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.localizers;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.PedroConstants.BACK_ENCODER;
|
||||
import static org.firstinspires.ftc.teamcode.PedroConstants.LEFT_ENCODER;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.HardwareMap;
|
||||
import com.qualcomm.robotcore.hardware.IMU;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Encoder;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Localizer;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Matrix;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.NanoTimer;
|
||||
|
||||
/**
|
||||
* This is the TwoWheelLocalizer class. This class extends the Localizer superclass and is a
|
||||
* localizer that uses the two wheel odometry with IMU set up. The diagram below, which is modified from
|
||||
* Road Runner, shows a typical set up.
|
||||
*
|
||||
* The view is from the top of the robot looking downwards.
|
||||
*
|
||||
* left on robot is the y positive direction
|
||||
*
|
||||
* forward on robot is the x positive direction
|
||||
*
|
||||
* /--------------\
|
||||
* | ____ |
|
||||
* | ---- |
|
||||
* | || || |
|
||||
* | || || | ----> left (y positive)
|
||||
* | |
|
||||
* | |
|
||||
* \--------------/
|
||||
* |
|
||||
* |
|
||||
* V
|
||||
* forward (x positive)
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/2/2024
|
||||
*/
|
||||
@Config
|
||||
public class TwoWheelLocalizer extends Localizer { // todo: make two wheel odo work
|
||||
private HardwareMap hardwareMap;
|
||||
private IMU imu;
|
||||
private Pose startPose;
|
||||
private Pose displacementPose;
|
||||
private Pose currentVelocity;
|
||||
private Matrix prevRotationMatrix;
|
||||
private NanoTimer timer;
|
||||
private long deltaTimeNano;
|
||||
private Encoder forwardEncoder;
|
||||
private Encoder strafeEncoder;
|
||||
private Pose forwardEncoderPose;
|
||||
private Pose strafeEncoderPose;
|
||||
private double previousIMUOrientation;
|
||||
private double deltaRadians;
|
||||
private double totalHeading;
|
||||
public static double FORWARD_TICKS_TO_INCHES = 8192 * 1.37795 * 2 * Math.PI * 0.5008239963;
|
||||
public static double STRAFE_TICKS_TO_INCHES = 8192 * 1.37795 * 2 * Math.PI * 0.5018874659;
|
||||
|
||||
/**
|
||||
* This creates a new TwoWheelLocalizer from a HardwareMap, with a starting Pose at (0,0)
|
||||
* facing 0 heading.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
*/
|
||||
public TwoWheelLocalizer(HardwareMap map) {
|
||||
this(map, new Pose());
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new TwoWheelLocalizer from a HardwareMap and a Pose, with the Pose
|
||||
* specifying the starting pose of the localizer.
|
||||
*
|
||||
* @param map the HardwareMap
|
||||
* @param setStartPose the Pose to start from
|
||||
*/
|
||||
public TwoWheelLocalizer(HardwareMap map, Pose setStartPose) {
|
||||
// TODO: replace these with your encoder positions
|
||||
forwardEncoderPose = new Pose(-18.5/25.4 - 0.1, 164.4/25.4, 0);
|
||||
strafeEncoderPose = new Pose(-107.9/25.4+0.25, -1.1/25.4-0.23, Math.toRadians(90));
|
||||
|
||||
hardwareMap = map;
|
||||
|
||||
imu = hardwareMap.get(IMU.class, "imu");
|
||||
// TODO: replace this with your IMU's orientation
|
||||
imu.initialize(new IMU.Parameters(new RevHubOrientationOnRobot(RevHubOrientationOnRobot.LogoFacingDirection.UP, RevHubOrientationOnRobot.UsbFacingDirection.LEFT)));
|
||||
|
||||
// TODO: replace these with your encoder ports
|
||||
forwardEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, LEFT_ENCODER));
|
||||
strafeEncoder = new Encoder(hardwareMap.get(DcMotorEx.class, BACK_ENCODER));
|
||||
|
||||
// TODO: reverse any encoders necessary
|
||||
forwardEncoder.setDirection(Encoder.REVERSE);
|
||||
strafeEncoder.setDirection(Encoder.FORWARD);
|
||||
|
||||
setStartPose(setStartPose);
|
||||
timer = new NanoTimer();
|
||||
deltaTimeNano = 1;
|
||||
displacementPose = new Pose();
|
||||
currentVelocity = new Pose();
|
||||
|
||||
previousIMUOrientation = MathFunctions.normalizeAngle(imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS));
|
||||
deltaRadians = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current pose estimate.
|
||||
*
|
||||
* @return returns the current pose estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getPose() {
|
||||
return MathFunctions.addPoses(startPose, displacementPose);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Pose
|
||||
*/
|
||||
@Override
|
||||
public Pose getVelocity() {
|
||||
return currentVelocity.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current velocity estimate.
|
||||
*
|
||||
* @return returns the current velocity estimate as a Vector
|
||||
*/
|
||||
@Override
|
||||
public Vector getVelocityVector() {
|
||||
return currentVelocity.getVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the start pose. Changing the start pose should move the robot as if all its
|
||||
* previous movements were displacing it from its new start pose.
|
||||
*
|
||||
* @param setStart the new start pose
|
||||
*/
|
||||
@Override
|
||||
public void setStartPose(Pose setStart) {
|
||||
startPose = setStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the Matrix that contains the previous pose's heading rotation.
|
||||
*
|
||||
* @param heading the rotation of the Matrix
|
||||
*/
|
||||
public void setPrevRotationMatrix(double heading) {
|
||||
prevRotationMatrix = new Matrix(3,3);
|
||||
prevRotationMatrix.set(0, 0, Math.cos(heading));
|
||||
prevRotationMatrix.set(0, 1, -Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 0, Math.sin(heading));
|
||||
prevRotationMatrix.set(1, 1, Math.cos(heading));
|
||||
prevRotationMatrix.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the current pose estimate. Changing this should just change the robot's current
|
||||
* pose estimate, not anything to do with the start pose.
|
||||
*
|
||||
* @param setPose the new current pose estimate
|
||||
*/
|
||||
@Override
|
||||
public void setPose(Pose setPose) {
|
||||
displacementPose = MathFunctions.subtractPoses(setPose, startPose);
|
||||
resetEncoders();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the elapsed time timer that keeps track of time between updates, as well as the
|
||||
* change position of the Encoders and the IMU readings. Then, the robot's global change in
|
||||
* position is calculated using the pose exponential method.
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
deltaTimeNano = timer.getElapsedTime();
|
||||
timer.resetTimer();
|
||||
|
||||
updateEncoders();
|
||||
Matrix robotDeltas = getRobotDeltas();
|
||||
Matrix globalDeltas;
|
||||
setPrevRotationMatrix(getPose().getHeading());
|
||||
|
||||
Matrix transformation = new Matrix(3,3);
|
||||
if (Math.abs(robotDeltas.get(2, 0)) < 0.001) {
|
||||
transformation.set(0, 0, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(0, 1, -robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 0, robotDeltas.get(2, 0) / 2.0);
|
||||
transformation.set(1, 1, 1.0 - (Math.pow(robotDeltas.get(2, 0), 2) / 6.0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
} else {
|
||||
transformation.set(0, 0, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(0, 1, (Math.cos(robotDeltas.get(2, 0)) - 1.0) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 0, (1.0 - Math.cos(robotDeltas.get(2, 0))) / robotDeltas.get(2, 0));
|
||||
transformation.set(1, 1, Math.sin(robotDeltas.get(2, 0)) / robotDeltas.get(2, 0));
|
||||
transformation.set(2, 2, 1.0);
|
||||
}
|
||||
|
||||
globalDeltas = Matrix.multiply(Matrix.multiply(prevRotationMatrix, transformation), robotDeltas);
|
||||
|
||||
displacementPose.add(new Pose(globalDeltas.get(0, 0), globalDeltas.get(1, 0), globalDeltas.get(2, 0)));
|
||||
currentVelocity = new Pose(globalDeltas.get(0, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(1, 0) / (deltaTimeNano * Math.pow(10.0, 9)), globalDeltas.get(2, 0) / (deltaTimeNano * Math.pow(10.0, 9)));
|
||||
|
||||
totalHeading += globalDeltas.get(2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the Encoders as well as the IMU.
|
||||
*/
|
||||
public void updateEncoders() {
|
||||
forwardEncoder.update();
|
||||
strafeEncoder.update();
|
||||
|
||||
double currentIMUOrientation = MathFunctions.normalizeAngle(imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.RADIANS));
|
||||
deltaRadians = MathFunctions.getTurnDirection(previousIMUOrientation, currentIMUOrientation) * MathFunctions.getSmallestAngleDifference(currentIMUOrientation, previousIMUOrientation);
|
||||
previousIMUOrientation = currentIMUOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the Encoders.
|
||||
*/
|
||||
public void resetEncoders() {
|
||||
forwardEncoder.reset();
|
||||
strafeEncoder.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This calculates the change in position from the perspective of the robot using information
|
||||
* from the Encoders and IMU.
|
||||
*
|
||||
* @return returns a Matrix containing the robot relative movement.
|
||||
*/
|
||||
public Matrix getRobotDeltas() {
|
||||
Matrix returnMatrix = new Matrix(3,1);
|
||||
// x/forward movement
|
||||
returnMatrix.set(0,0, FORWARD_TICKS_TO_INCHES * (forwardEncoder.getDeltaPosition() - forwardEncoderPose.getY() * deltaRadians));
|
||||
//y/strafe movement
|
||||
returnMatrix.set(1,0, STRAFE_TICKS_TO_INCHES * (strafeEncoder.getDeltaPosition() - strafeEncoderPose.getX() * deltaRadians));
|
||||
// theta/turning
|
||||
returnMatrix.set(2,0, deltaRadians);
|
||||
return returnMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns how far the robot has turned in radians, in a number not clamped between 0 and
|
||||
* 2 * pi radians. This is used for some tuning things and nothing actually within the following.
|
||||
*
|
||||
* @return returns how far the robot has turned in total, in radians.
|
||||
*/
|
||||
public double getTotalHeading() {
|
||||
return totalHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to forward movement measurement to convert from encoder
|
||||
* ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the forward ticks to inches multiplier
|
||||
*/
|
||||
public double getForwardMultiplier() {
|
||||
return FORWARD_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to lateral/strafe movement measurement to convert from
|
||||
* encoder ticks to inches. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the lateral/strafe ticks to inches multiplier
|
||||
*/
|
||||
public double getLateralMultiplier() {
|
||||
return STRAFE_TICKS_TO_INCHES;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the multiplier applied to turning movement measurement to convert from encoder
|
||||
* ticks to radians. This is found empirically through a tuner.
|
||||
*
|
||||
* @return returns the turning ticks to radians multiplier
|
||||
*/
|
||||
public double getTurningMultiplier() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the IMU.
|
||||
*/
|
||||
public void resetIMU() {
|
||||
imu.resetYaw();
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.tuning;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.DashboardPoseTracker;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.Drawing;
|
||||
|
||||
/**
|
||||
* This is the ForwardTuner OpMode. This tracks the forward movement of the robot and displays the
|
||||
* necessary ticks to inches multiplier. This displayed multiplier is what's necessary to scale the
|
||||
* robot's current distance in ticks to the specified distance in inches. So, to use this, run the
|
||||
* tuner, then pull/push the robot to the specified distance using a ruler on the ground. When you're
|
||||
* at the end of the distance, record the ticks to inches multiplier. Feel free to run multiple trials
|
||||
* and average the results. Then, input the multiplier into the forward ticks to inches in your
|
||||
* localizer of choice.
|
||||
* You can adjust the target distance on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 5/6/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(name = "Forward Localizer Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class FowardTuner extends OpMode {
|
||||
private PoseUpdater poseUpdater;
|
||||
private DashboardPoseTracker dashboardPoseTracker;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
public static double DISTANCE = 30;
|
||||
|
||||
/**
|
||||
* This initializes the PoseUpdater as well as the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
dashboardPoseTracker = new DashboardPoseTracker(poseUpdater);
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("Pull your robot forward " + DISTANCE + " inches. Your forward ticks to inches will be shown on the telemetry.");
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the robot's pose estimate, and updates the FTC Dashboard telemetry with the
|
||||
* calculated multiplier and draws the robot.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
poseUpdater.update();
|
||||
|
||||
telemetryA.addData("distance moved", poseUpdater.getPose().getX());
|
||||
telemetryA.addLine("The multiplier will display what your forward ticks to inches should be to scale your current distance to " + DISTANCE + " inches.");
|
||||
telemetryA.addData("multiplier", DISTANCE / (poseUpdater.getPose().getX() / poseUpdater.getLocalizer().getForwardMultiplier()));
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawPoseHistory(dashboardPoseTracker, "#4CAF50");
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.tuning;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.DashboardPoseTracker;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.Drawing;
|
||||
|
||||
/**
|
||||
* This is the LateralTuner OpMode. This tracks the strafe movement of the robot and displays the
|
||||
* necessary ticks to inches multiplier. This displayed multiplier is what's necessary to scale the
|
||||
* robot's current distance in ticks to the specified distance in inches. So, to use this, run the
|
||||
* tuner, then pull/push the robot to the specified distance using a ruler on the ground. When you're
|
||||
* at the end of the distance, record the ticks to inches multiplier. Feel free to run multiple trials
|
||||
* and average the results. Then, input the multiplier into the strafe ticks to inches in your
|
||||
* localizer of choice.
|
||||
* You can adjust the target distance on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 5/6/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(name = "Lateral Localizer Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class LateralTuner extends OpMode {
|
||||
private PoseUpdater poseUpdater;
|
||||
private DashboardPoseTracker dashboardPoseTracker;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
public static double DISTANCE = 30;
|
||||
|
||||
/**
|
||||
* This initializes the PoseUpdater as well as the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
dashboardPoseTracker = new DashboardPoseTracker(poseUpdater);
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("Pull your robot to the right " + DISTANCE + " inches. Your strafe ticks to inches will be shown on the telemetry.");
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the robot's pose estimate, and updates the FTC Dashboard telemetry with the
|
||||
* calculated multiplier and draws the robot.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
poseUpdater.update();
|
||||
|
||||
telemetryA.addData("distance moved", poseUpdater.getPose().getY());
|
||||
telemetryA.addLine("The multiplier will display what your strafe ticks to inches should be to scale your current distance to " + DISTANCE + " inches.");
|
||||
telemetryA.addData("multiplier", DISTANCE / (poseUpdater.getPose().getY() / poseUpdater.getLocalizer().getLateralMultiplier()));
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawPoseHistory(dashboardPoseTracker, "#4CAF50");
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.tuning;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
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.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.DashboardPoseTracker;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.Drawing;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the LocalizationTest OpMode. This is basically just a simple mecanum drive attached to a
|
||||
* PoseUpdater. The OpMode will print out the robot's pose to telemetry as well as draw the robot
|
||||
* on FTC Dashboard (192/168/43/1:8080/dash). You should use this to check the robot's localization.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 5/6/2024
|
||||
*/
|
||||
@Config
|
||||
@TeleOp(group = "Pedro Pathing Tuning", name = "Localization Test")
|
||||
public class LocalizationTest extends OpMode {
|
||||
private PoseUpdater poseUpdater;
|
||||
private DashboardPoseTracker dashboardPoseTracker;
|
||||
private Telemetry telemetryA;
|
||||
|
||||
private DcMotorEx leftFront;
|
||||
private DcMotorEx leftRear;
|
||||
private DcMotorEx rightFront;
|
||||
private DcMotorEx rightRear;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
/**
|
||||
* This initializes the PoseUpdater, the mecanum drive motors, and the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
dashboardPoseTracker = new DashboardPoseTracker(poseUpdater);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, leftFrontMotorName);
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, leftRearMotorName);
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, rightRearMotorName);
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, rightFrontMotorName);
|
||||
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightFront, rightRear);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT);
|
||||
}
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("This will print your robot's position to telemetry while "
|
||||
+ "allowing robot control through a basic mecanum drive on gamepad 1.");
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the robot's pose estimate, the simple mecanum drive, and updates the FTC
|
||||
* Dashboard telemetry with the robot's position as well as draws the robot's position.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
poseUpdater.update();
|
||||
dashboardPoseTracker.update();
|
||||
|
||||
double y = -gamepad1.left_stick_y; // Remember, this is reversed!
|
||||
double x = gamepad1.left_stick_x; // this is strafing
|
||||
double rx = gamepad1.right_stick_x;
|
||||
|
||||
// Denominator is the largest motor power (absolute value) or 1
|
||||
// This ensures all the powers maintain the same ratio, but only when
|
||||
// at least one is out of the range [-1, 1]
|
||||
double denominator = Math.max(Math.abs(y) + Math.abs(x) + Math.abs(rx), 1);
|
||||
double leftFrontPower = (y + x + rx) / denominator;
|
||||
double leftRearPower = (y - x + rx) / denominator;
|
||||
double rightFrontPower = (y - x - rx) / denominator;
|
||||
double rightRearPower = (y + x - rx) / denominator;
|
||||
|
||||
leftFront.setPower(leftFrontPower);
|
||||
leftRear.setPower(leftRearPower);
|
||||
rightFront.setPower(rightFrontPower);
|
||||
rightRear.setPower(rightRearPower);
|
||||
|
||||
telemetryA.addData("x", poseUpdater.getPose().getX());
|
||||
telemetryA.addData("y", poseUpdater.getPose().getY());
|
||||
telemetryA.addData("heading", poseUpdater.getPose().getHeading());
|
||||
telemetryA.addData("total heading", poseUpdater.getTotalHeading());
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawPoseHistory(dashboardPoseTracker, "#4CAF50");
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.localization.tuning;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.DashboardPoseTracker;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.Drawing;
|
||||
|
||||
/**
|
||||
* This is the TurnTuner OpMode. This tracks the turning movement of the robot and displays the
|
||||
* necessary ticks to inches multiplier. This displayed multiplier is what's necessary to scale the
|
||||
* robot's current angle in ticks to the specified angle in radians. So, to use this, run the
|
||||
* tuner, then pull/push the robot to the specified angle using a protractor or lines on the ground.
|
||||
* When you're at the end of the angle, record the ticks to inches multiplier. Feel free to run
|
||||
* multiple trials and average the results. Then, input the multiplier into the turning ticks to
|
||||
* radians in your localizer of choice.
|
||||
* You can adjust the target angle on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 5/6/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous(name = "Turn Localizer Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class TurnTuner extends OpMode {
|
||||
private PoseUpdater poseUpdater;
|
||||
private DashboardPoseTracker dashboardPoseTracker;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
public static double ANGLE = 2 * Math.PI;
|
||||
|
||||
/**
|
||||
* This initializes the PoseUpdater as well as the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
dashboardPoseTracker = new DashboardPoseTracker(poseUpdater);
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("Turn your robot " + ANGLE + " radians. Your turn ticks to inches will be shown on the telemetry.");
|
||||
telemetryA.update();
|
||||
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the robot's pose estimate, and updates the FTC Dashboard telemetry with the
|
||||
* calculated multiplier and draws the robot.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
poseUpdater.update();
|
||||
|
||||
telemetryA.addData("total angle", poseUpdater.getTotalHeading());
|
||||
telemetryA.addLine("The multiplier will display what your turn ticks to inches should be to scale your current angle to " + ANGLE + " radians.");
|
||||
telemetryA.addData("multiplier", ANGLE / (poseUpdater.getTotalHeading() / poseUpdater.getLocalizer().getTurningMultiplier()));
|
||||
|
||||
Drawing.drawPoseHistory(dashboardPoseTracker, "#4CAF50");
|
||||
Drawing.drawRobot(poseUpdater.getPose(), "#4CAF50");
|
||||
Drawing.sendPacket();
|
||||
}
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This is the BezierCurve class. This class handles the creation of Bezier curves, which are used
|
||||
* as the basis of the path for the Path class. Bezier curves are parametric curves defined by a set
|
||||
* of control points. So, they take in one input, t, that ranges from [0, 1] and that returns a point
|
||||
* on the curve. Essentially, Bezier curves are a way of defining a parametric line easily. You can
|
||||
* read more on Bezier curves here: https://en.wikipedia.org/wiki/Bézier_curve
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/5/2024
|
||||
*/
|
||||
public class BezierCurve {
|
||||
// This contains the coefficients for the curve points
|
||||
private ArrayList<BezierCurveCoefficients> pointCoefficients = new ArrayList<>();
|
||||
|
||||
// This contains the control points for the Bezier curve
|
||||
private ArrayList<Point> controlPoints = new ArrayList<>();
|
||||
|
||||
private Vector endTangent = new Vector();
|
||||
|
||||
private final int APPROXIMATION_STEPS = FollowerConstants.APPROXIMATION_STEPS;
|
||||
|
||||
private final int DASHBOARD_DRAWING_APPROXIMATION_STEPS = 100;
|
||||
|
||||
private double[][] dashboardDrawingPoints;
|
||||
|
||||
private double UNIT_TO_TIME;
|
||||
private double length;
|
||||
|
||||
/**
|
||||
* This creates an empty BezierCurve.
|
||||
* IMPORTANT NOTE: Only use this for the constructors of classes extending this. If you try to
|
||||
* run the robot on a Path containing an empty BezierCurve, then it will explode.
|
||||
*/
|
||||
public BezierCurve() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new BezierCurve with an ArrayList of control points and generates the curve.
|
||||
* IMPORTANT NOTE: The order of the control points is important. That's the order the code will
|
||||
* process them in, with the 0 index being the start point and the final index being the end point
|
||||
*
|
||||
* @param controlPoints This is the ArrayList of control points that define the BezierCurve.
|
||||
*/
|
||||
public BezierCurve(ArrayList<Point> controlPoints) {
|
||||
if (controlPoints.size()<3) {
|
||||
try {
|
||||
throw new Exception("Too few control points");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.controlPoints = controlPoints;
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Bezier curve with some specified control points and generates the curve.
|
||||
* IMPORTANT NOTE: The order of the control points is important. That's the order the code will
|
||||
* process them in, with the 0 index being the start point and the final index being the end point.
|
||||
*
|
||||
* @param controlPoints This is the specified control points that define the BezierCurve.
|
||||
*/
|
||||
public BezierCurve(Point... controlPoints) {
|
||||
for (Point controlPoint : controlPoints) {
|
||||
this.controlPoints.add(controlPoint);
|
||||
}
|
||||
if (this.controlPoints.size()<3) {
|
||||
try {
|
||||
throw new Exception("Too few control points");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles most of the initialization of the BezierCurve that is called from the constructor.
|
||||
*/
|
||||
public void initialize() {
|
||||
generateBezierCurve();
|
||||
length = approximateLength();
|
||||
UNIT_TO_TIME = 1/length;
|
||||
endTangent.setOrthogonalComponents(controlPoints.get(controlPoints.size()-1).getX()-controlPoints.get(controlPoints.size()-2).getX(), controlPoints.get(controlPoints.size()-1).getY()-controlPoints.get(controlPoints.size()-2).getY());
|
||||
endTangent = MathFunctions.normalizeVector(endTangent);
|
||||
initializeDashboardDrawingPoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates the Array that holds the Points to draw on the Dashboard.
|
||||
*/
|
||||
public void initializeDashboardDrawingPoints() {
|
||||
dashboardDrawingPoints = new double[2][DASHBOARD_DRAWING_APPROXIMATION_STEPS + 1];
|
||||
for (int i = 0; i <= DASHBOARD_DRAWING_APPROXIMATION_STEPS; i++) {
|
||||
Point currentPoint = getPoint(i/(double) (DASHBOARD_DRAWING_APPROXIMATION_STEPS));
|
||||
dashboardDrawingPoints[0][i] = currentPoint.getX();
|
||||
dashboardDrawingPoints[1][i] = currentPoint.getY();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a 2D Array of doubles containing the x and y positions of points to draw on FTC
|
||||
* Dashboard.
|
||||
*
|
||||
* @return returns the 2D Array to draw on FTC Dashboard
|
||||
*/
|
||||
public double[][] getDashboardDrawingPoints() {
|
||||
return dashboardDrawingPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* This generates the Bezier curve. It assumes that the ArrayList of control points has been set.
|
||||
* Well, this actually generates the coefficients for each control point on the Bezier curve.
|
||||
* These coefficients can then be used to calculate a position, velocity, or accleration on the
|
||||
* Bezier curve on the fly without much computational expense.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Bézier_curve for the explicit formula for Bezier curves
|
||||
*/
|
||||
public void generateBezierCurve() {
|
||||
int n = controlPoints.size()-1;
|
||||
for (int i = 0; i <= n; i++) {
|
||||
pointCoefficients.add(new BezierCurveCoefficients(n, i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the unit tangent Vector at the end of the BezierCurve.
|
||||
*
|
||||
* @return returns the end tangent Vector.
|
||||
*/
|
||||
public Vector getEndTangent() {
|
||||
return MathFunctions.copyVector(endTangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This approximates the length of the BezierCurve in APPROXIMATION_STEPS number of steps. It's
|
||||
* like a Riemann's sum, but for a parametric function's arc length.
|
||||
*
|
||||
* @return returns the approximated length of the BezierCurve.
|
||||
*/
|
||||
public double approximateLength() {
|
||||
Point previousPoint = getPoint(0);
|
||||
Point currentPoint;
|
||||
double approxLength = 0;
|
||||
for (int i = 1; i <= APPROXIMATION_STEPS; i++) {
|
||||
currentPoint = getPoint(i/(double)APPROXIMATION_STEPS);
|
||||
approxLength += previousPoint.distanceFrom(currentPoint);
|
||||
previousPoint = currentPoint;
|
||||
}
|
||||
return approxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the point on the Bezier curve that is specified by the parametric t value. A
|
||||
* Bezier curve is a parametric function that returns points along it with t ranging from [0, 1],
|
||||
* with 0 being the beginning of the curve and 1 being at the end. The Follower will follow
|
||||
* BezierCurves from 0 to 1, in terms of t.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the point requested.
|
||||
*/
|
||||
public Point getPoint(double t) {
|
||||
t = MathFunctions.clamp(t, 0, 1);
|
||||
double xCoordinate = 0;
|
||||
double yCoordinate = 0;
|
||||
|
||||
// calculates the x coordinate of the point requested
|
||||
for (int i = 0; i < controlPoints.size(); i++) {
|
||||
xCoordinate += pointCoefficients.get(i).getValue(t) * controlPoints.get(i).getX();
|
||||
}
|
||||
|
||||
// calculates the y coordinate of the point requested
|
||||
for (int i = 0; i < controlPoints.size(); i++) {
|
||||
yCoordinate += pointCoefficients.get(i).getValue(t) * controlPoints.get(i).getY();
|
||||
}
|
||||
return new Point(xCoordinate, yCoordinate, Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the curvature of the Bezier curve at a specified t-value.
|
||||
*
|
||||
* @param t the parametric t input.
|
||||
* @return returns the curvature.
|
||||
*/
|
||||
public double getCurvature(double t) {
|
||||
t = MathFunctions.clamp(t, 0, 1);
|
||||
Vector derivative = getDerivative(t);
|
||||
Vector secondDerivative = getSecondDerivative(t);
|
||||
|
||||
if (derivative.getMagnitude() == 0) return 0;
|
||||
return (MathFunctions.crossProduct(derivative, secondDerivative))/Math.pow(derivative.getMagnitude(),3);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the derivative on the BezierCurve that is specified by the parametric t value.
|
||||
* This is returned as a Vector, and this Vector is the tangent to the BezierCurve.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the derivative requested.
|
||||
*/
|
||||
public Vector getDerivative(double t) {
|
||||
t = MathFunctions.clamp(t, 0, 1);
|
||||
double xCoordinate = 0;
|
||||
double yCoordinate = 0;
|
||||
Vector returnVector = new Vector();
|
||||
|
||||
// calculates the x coordinate of the point requested
|
||||
for (int i = 0; i < controlPoints.size()-1; i++) {
|
||||
xCoordinate += pointCoefficients.get(i).getDerivativeValue(t) * (MathFunctions.subtractPoints(controlPoints.get(i+1), controlPoints.get(i)).getX());
|
||||
}
|
||||
|
||||
// calculates the y coordinate of the point requested
|
||||
for (int i = 0; i < controlPoints.size()-1; i++) {;
|
||||
yCoordinate += pointCoefficients.get(i).getDerivativeValue(t) * (MathFunctions.subtractPoints(controlPoints.get(i+1), controlPoints.get(i)).getY());
|
||||
}
|
||||
|
||||
returnVector.setOrthogonalComponents(xCoordinate, yCoordinate);
|
||||
|
||||
return returnVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the second derivative on the BezierCurve that is specified by the parametric t value.
|
||||
* This is returned as a Vector, and this Vector is the acceleration on the BezierCurve.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the second derivative requested.
|
||||
*/
|
||||
public Vector getSecondDerivative(double t) {
|
||||
t = MathFunctions.clamp(t, 0, 1);
|
||||
double xCoordinate = 0;
|
||||
double yCoordinate = 0;
|
||||
Vector returnVector = new Vector();
|
||||
|
||||
// calculates the x coordinate of the point requested
|
||||
for (int i = 0; i < controlPoints.size()-2; i++) {
|
||||
xCoordinate += pointCoefficients.get(i).getSecondDerivativeValue(t) * (MathFunctions.addPoints(MathFunctions.subtractPoints(controlPoints.get(i+2), new Point(2*controlPoints.get(i+1).getX(), 2*controlPoints.get(i+1).getY(), Point.CARTESIAN)), controlPoints.get(i)).getX());
|
||||
}
|
||||
|
||||
// calculates the y coordinate of the point requested
|
||||
for (int i = 0; i < controlPoints.size()-2; i++) {
|
||||
yCoordinate += pointCoefficients.get(i).getSecondDerivativeValue(t) * (MathFunctions.addPoints(MathFunctions.subtractPoints(controlPoints.get(i+2), new Point(2*controlPoints.get(i+1).getX(), 2*controlPoints.get(i+1).getY(), Point.CARTESIAN)), controlPoints.get(i)).getY());
|
||||
}
|
||||
|
||||
returnVector.setOrthogonalComponents(xCoordinate, yCoordinate);
|
||||
|
||||
return returnVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because, for whatever reason, the second derivative returned by the getSecondDerivative(double t)
|
||||
* method doesn't return the correct heading of the second derivative, this gets an approximate
|
||||
* second derivative essentially using the limit method. I use this for its heading only.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the approximated second derivative.
|
||||
*/
|
||||
public Vector getApproxSecondDerivative(double t) {
|
||||
double current = getDerivative(t).getTheta();
|
||||
double deltaCurrent = getDerivative(t + 0.0001).getTheta();
|
||||
|
||||
return new Vector(1, deltaCurrent - current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ArrayList of control points for this BezierCurve.
|
||||
*
|
||||
* @return This returns the control points.
|
||||
*/
|
||||
public ArrayList<Point> getControlPoints() {
|
||||
return controlPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first control point for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getFirstControlPoint() {
|
||||
return controlPoints.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second control point, or the one after the start, for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getSecondControlPoint() {
|
||||
return controlPoints.get(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second to last control point for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getSecondToLastControlPoint() {
|
||||
return controlPoints.get(controlPoints.size()-2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last control point for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getLastControlPoint() {
|
||||
return controlPoints.get(controlPoints.size()-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the approximate length of this BezierCurve.
|
||||
*
|
||||
* @return This returns the length.
|
||||
*/
|
||||
public double length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conversion factor of one unit of distance into t-value. Since parametric functions
|
||||
* are defined by t, which can mean time, I use "time" in some method names for conciseness.
|
||||
*
|
||||
* @return returns the conversion factor.
|
||||
*/
|
||||
public double UNIT_TO_TIME() {
|
||||
return UNIT_TO_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of path. This is used in case we need to identify the type of BezierCurve
|
||||
* this is.
|
||||
*
|
||||
* @return returns the type of path.
|
||||
*/
|
||||
public String pathType() {
|
||||
return "curve";
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
/**
|
||||
* This is the BezierCurveCoefficients class. This class handles holding the coefficients for each
|
||||
* control point for the BezierCurve class to allow for faster on the fly calculations of points,
|
||||
* derivatives, and second derivatives on Bezier curves.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/8/2024
|
||||
*/
|
||||
public class BezierCurveCoefficients {
|
||||
private double coefficient;
|
||||
private double derivativeCoefficient;
|
||||
private double secondDerivativeCoefficient;
|
||||
|
||||
private int n;
|
||||
private int i;
|
||||
|
||||
/**
|
||||
* This creates the coefficients within the summation equations for calculating positions,
|
||||
* derivatives, and second derivatives on Bezier curves.
|
||||
*
|
||||
* @param n this is the degree of the Bezier curve function.
|
||||
* @param i this is the i within the summation equation, so basically which place it is in the
|
||||
* summation.
|
||||
*/
|
||||
public BezierCurveCoefficients(int n, int i) {
|
||||
this.n = n;
|
||||
this.i = i;
|
||||
coefficient = MathFunctions.nCr(n, i);
|
||||
derivativeCoefficient = MathFunctions.nCr(n - 1, i);
|
||||
secondDerivativeCoefficient = MathFunctions.nCr(n - 2, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the coefficient for the summation to calculate a position on BezierCurves.
|
||||
*
|
||||
* @param t this is the t value within the parametric equation for the Bezier curve.
|
||||
* @return this returns the coefficient.
|
||||
*/
|
||||
public double getValue(double t) {
|
||||
return coefficient * Math.pow(1 - t, n - i) * Math.pow(t, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the coefficient for the summation to calculate a derivative on BezierCurves.
|
||||
*
|
||||
* @param t this is the t value within the parametric equation for the Bezier curve.
|
||||
* @return this returns the coefficient.
|
||||
*/
|
||||
public double getDerivativeValue(double t) {
|
||||
return n * derivativeCoefficient * Math.pow(t, i) * Math.pow(1 - t, n - i - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the coefficient for the summation to calculate a second derivative on BezierCurves.
|
||||
*
|
||||
* @param t this is the t value within the parametric equation for the Bezier curve.
|
||||
* @return this returns the coefficient.
|
||||
*/
|
||||
public double getSecondDerivativeValue(double t) {
|
||||
return n * (n - 1) * secondDerivativeCoefficient * Math.pow(t, i) * Math.pow(1 - t, n - i - 2);
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is the BezierLine class. This class handles the creation of BezierLines, which is what I
|
||||
* call Bezier curves with only two control points. The parent BezierCurve class cannot handle Bezier
|
||||
* curves with less than three control points, so this class handles lines. Additionally, it makes
|
||||
* the calculations done on the fly a little less computationally expensive and more streamlined.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/9/2024
|
||||
*/
|
||||
public class BezierLine extends BezierCurve {
|
||||
|
||||
private Point startPoint;
|
||||
private Point endPoint;
|
||||
|
||||
private Vector endTangent;
|
||||
|
||||
private double UNIT_TO_TIME;
|
||||
private double length;
|
||||
|
||||
/**
|
||||
* This creates a new BezierLine with specified start and end Points.
|
||||
* This is just a line but it extends the BezierCurve class so things work.
|
||||
*
|
||||
* @param startPoint start point of the line.
|
||||
* @param endPoint end point of the line.
|
||||
*/
|
||||
public BezierLine(Point startPoint, Point endPoint) {
|
||||
super();
|
||||
this.startPoint = startPoint;
|
||||
this.endPoint = endPoint;
|
||||
length = approximateLength();
|
||||
UNIT_TO_TIME = 1 / length;
|
||||
endTangent = MathFunctions.normalizeVector(getDerivative(1));
|
||||
super.initializeDashboardDrawingPoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the unit tangent Vector at the end of the BezierLine.
|
||||
*
|
||||
* @return returns the tangent Vector.
|
||||
*/
|
||||
@Override
|
||||
public Vector getEndTangent() {
|
||||
return MathFunctions.copyVector(endTangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the length of the BezierLine.
|
||||
*
|
||||
* @return returns the length of the BezierLine.
|
||||
*/
|
||||
@Override
|
||||
public double approximateLength() {
|
||||
return Math.sqrt(Math.pow(startPoint.getX() - endPoint.getX(), 2) + Math.pow(startPoint.getY() - endPoint.getY(), 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the Point on the Bezier line that is specified by the parametric t value.
|
||||
*
|
||||
* @param t this is the t value of the parametric line. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the Point requested.
|
||||
*/
|
||||
@Override
|
||||
public Point getPoint(double t) {
|
||||
t = MathFunctions.clamp(t, 0, 1);
|
||||
return new Point((endPoint.getX() - startPoint.getX()) * t + startPoint.getX(), (endPoint.getY() - startPoint.getY()) * t + startPoint.getY(), Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the curvature of the BezierLine, which is zero.
|
||||
*
|
||||
* @param t the parametric t value.
|
||||
* @return returns the curvature.
|
||||
*/
|
||||
@Override
|
||||
public double getCurvature(double t) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the derivative on the BezierLine as a Vector, which is a constant slope.
|
||||
* The t value doesn't really do anything, but it's there so I can override methods.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the derivative requested.
|
||||
*/
|
||||
@Override
|
||||
public Vector getDerivative(double t) {
|
||||
Vector returnVector = new Vector();
|
||||
|
||||
returnVector.setOrthogonalComponents(endPoint.getX() - startPoint.getX(), endPoint.getY() - startPoint.getY());
|
||||
|
||||
return returnVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the second derivative on the Bezier line, which is a zero Vector.
|
||||
* Once again, the t is only there for the override.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the second derivative requested.
|
||||
*/
|
||||
@Override
|
||||
public Vector getSecondDerivative(double t) {
|
||||
return new Vector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the zero Vector, but it's here so I can override the method in the BezierCurve
|
||||
* class.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the approximated second derivative, which is the zero Vector.
|
||||
*/
|
||||
@Override
|
||||
public Vector getApproxSecondDerivative(double t) {
|
||||
return new Vector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ArrayList of control points for this BezierLine.
|
||||
*
|
||||
* @return This returns the control points.
|
||||
*/
|
||||
@Override
|
||||
public ArrayList<Point> getControlPoints() {
|
||||
ArrayList<Point> returnList = new ArrayList<>();
|
||||
returnList.add(startPoint);
|
||||
returnList.add(endPoint);
|
||||
return returnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first control point for this BezierLine.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getFirstControlPoint() {
|
||||
return startPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second control point, or the one after the start, for this BezierLine.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getSecondControlPoint() {
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second to last control point for this BezierLine.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getSecondToLastControlPoint() {
|
||||
return startPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last control point for this BezierLine.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getLastControlPoint() {
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of this BezierLine.
|
||||
*
|
||||
* @return This returns the length.
|
||||
*/
|
||||
@Override
|
||||
public double length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conversion factor of one unit of distance into t value.
|
||||
*
|
||||
* @return returns the conversion factor.
|
||||
*/
|
||||
@Override
|
||||
public double UNIT_TO_TIME() {
|
||||
return UNIT_TO_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of path. This is used in case we need to identify the type of BezierCurve
|
||||
* this is.
|
||||
*
|
||||
* @return returns the type of path.
|
||||
*/
|
||||
@Override
|
||||
public String pathType() {
|
||||
return "line";
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is the BezierPoint class. This class handles the creation of BezierPoints, which is what I
|
||||
* call Bezier curves with only one control point. The parent BezierCurve class cannot handle Bezier
|
||||
* curves with less than three control points, so this class handles points. Additionally, it makes
|
||||
* the calculations done on the fly a little less computationally expensive and more streamlined.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/9/2024
|
||||
*/
|
||||
public class BezierPoint extends BezierCurve {
|
||||
|
||||
private Point point;
|
||||
|
||||
private Vector endTangent = new Vector();
|
||||
|
||||
private double UNIT_TO_TIME;
|
||||
private double length;
|
||||
|
||||
/**
|
||||
* This creates a new BezierPoint with a specified Point.
|
||||
* This is just a point but it extends the BezierCurve class so things work.
|
||||
*
|
||||
* @param point the specified point.
|
||||
*/
|
||||
public BezierPoint(Point point) {
|
||||
super();
|
||||
this.point = point;
|
||||
length = approximateLength();
|
||||
super.initializeDashboardDrawingPoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* This supposedly returns the unit tangent Vector at the end of the path, but since there is
|
||||
* no end tangent of a point, this returns a zero Vector instead. Holding BezierPoints in the
|
||||
* Follower doesn't use the drive Vector, so the end tangent Vector is not needed or truly used.
|
||||
*
|
||||
* @return returns the zero Vector.
|
||||
*/
|
||||
@Override
|
||||
public Vector getEndTangent() {
|
||||
return MathFunctions.copyVector(endTangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the length of the BezierPoint. Since points don't have length, this returns zero.
|
||||
*
|
||||
* @return returns the length of the BezierPoint.
|
||||
*/
|
||||
@Override
|
||||
public double approximateLength() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the point on the BezierPoint that is specified by the parametric t value. Since
|
||||
* this is a Point, this just returns the one control point's position.
|
||||
*
|
||||
* @param t this is the t value of the parametric line. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the Point requested.
|
||||
*/
|
||||
@Override
|
||||
public Point getPoint(double t) {
|
||||
return new Point(point.getX(), point.getY(), Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the curvature of the BezierPoint, which is zero since this is a Point.
|
||||
*
|
||||
* @param t the parametric t value.
|
||||
* @return returns the curvature, which is zero.
|
||||
*/
|
||||
@Override
|
||||
public double getCurvature(double t) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the derivative on the BezierPoint, which is the zero Vector since this is a Point.
|
||||
* The t value doesn't really do anything, but it's there so I can override methods.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the derivative requested, which is the zero Vector.
|
||||
*/
|
||||
@Override
|
||||
public Vector getDerivative(double t) {
|
||||
return MathFunctions.copyVector(endTangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the second derivative on the Bezier line, which is the zero Vector since this
|
||||
* is a Point.
|
||||
* Once again, the t is only there for the override.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the second derivative requested, which is the zero Vector.
|
||||
*/
|
||||
@Override
|
||||
public Vector getSecondDerivative(double t) {
|
||||
return new Vector();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the zero Vector, but it's here so I can override the method in the BezierCurve
|
||||
* class.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the approximated second derivative, which is the zero Vector.
|
||||
*/
|
||||
@Override
|
||||
public Vector getApproxSecondDerivative(double t) {
|
||||
return new Vector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ArrayList of control points for this BezierPoint
|
||||
*
|
||||
* @return This returns the control point.
|
||||
*/
|
||||
@Override
|
||||
public ArrayList<Point> getControlPoints() {
|
||||
ArrayList<Point> returnList = new ArrayList<>();
|
||||
returnList.add(point);
|
||||
return returnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first, and only, control point for this BezierPoint
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getFirstControlPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second control point, or the one after the start, for this BezierPoint. This
|
||||
* returns the one control point of the BezierPoint, and there are several redundant methods
|
||||
* that return the same control point, but it's here so I can override methods.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getSecondControlPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second to last control point for this BezierPoint. This
|
||||
* returns the one control point of the BezierPoint, and there are several redundant methods
|
||||
* that return the same control point, but it's here so I can override methods.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getSecondToLastControlPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last control point for this BezierPoint. This
|
||||
* returns the one control point of the BezierPoint, and there are several redundant methods
|
||||
* that return the same control point, but it's here so I can override methods.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
@Override
|
||||
public Point getLastControlPoint() {
|
||||
return point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of this BezierPoint, which is zero since Points don't have length.
|
||||
*
|
||||
* @return This returns the length.
|
||||
*/
|
||||
@Override
|
||||
public double length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conversion factor of one unit of distance into t value. There is no length or
|
||||
* conversion factor to speak of for Points.
|
||||
*
|
||||
* @return returns the conversion factor.
|
||||
*/
|
||||
@Override
|
||||
public double UNIT_TO_TIME() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of path. This is used in case we need to identify the type of BezierCurve
|
||||
* this is.
|
||||
*
|
||||
* @return returns the type of path.
|
||||
*/
|
||||
@Override
|
||||
public String pathType() {
|
||||
return "point";
|
||||
}
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
|
||||
/**
|
||||
* This is the MathFunctions class. This contains many useful math related methods that I use in
|
||||
* other classes to simplify code elsewhere.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/9/2024
|
||||
*/
|
||||
public class MathFunctions {
|
||||
|
||||
/**
|
||||
* This is a simple implementation of the choose function in math. It's equivalent to the number
|
||||
* of ways you can choose r items from n total items, if only which items got picked and not the
|
||||
* order of picking the items mattered.
|
||||
*
|
||||
* @param n this is how many you want to choose from.
|
||||
* @param r this is how many you want to choose.
|
||||
* @return returns the result of the "n choose r" function.
|
||||
*/
|
||||
public static double nCr(int n, int r) {
|
||||
double num = 1;
|
||||
double denom = 1;
|
||||
|
||||
// this multiplies up the numerator of the nCr function
|
||||
for (int i = n; i > n-r; i--) {
|
||||
num *= i;
|
||||
}
|
||||
|
||||
// this multiplies up the denominator of the nCr function
|
||||
for (int i = 1; i <=r; i++) {
|
||||
denom *= i;
|
||||
}
|
||||
|
||||
return num/denom;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the sign (positive/negative) of a number.
|
||||
*
|
||||
* @param get the number.
|
||||
* @return returns the sign of the number.
|
||||
*/
|
||||
public static double getSign(double get) {
|
||||
if (get == 0) return 0;
|
||||
if (get > 0) return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This clamps down a value to between the lower and upper bounds inclusive.
|
||||
*
|
||||
* @param num the number to be clamped.
|
||||
* @param lower the lower bound.
|
||||
* @param upper the upper bound.
|
||||
* @return returns the clamped number.
|
||||
*/
|
||||
public static double clamp(double num, double lower, double upper) {
|
||||
if (num < lower) return lower;
|
||||
if (num > upper) return upper;
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* This normalizes an angle to be between 0 and 2 pi radians, inclusive.
|
||||
*
|
||||
* IMPORTANT NOTE: This method operates in radians.
|
||||
*
|
||||
* @param angleRadians the angle to be normalized.
|
||||
* @return returns the normalized angle.
|
||||
*/
|
||||
public static double normalizeAngle(double angleRadians) {
|
||||
double angle = angleRadians;
|
||||
while (angle<0) angle += 2*Math.PI;
|
||||
while (angle>2*Math.PI) angle -= 2*Math.PI;
|
||||
return angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the smallest angle between two angles. This operates in radians.
|
||||
*
|
||||
* @param one one of the angles.
|
||||
* @param two the other one.
|
||||
* @return returns the smallest angle.
|
||||
*/
|
||||
public static double getSmallestAngleDifference(double one, double two) {
|
||||
return Math.min(MathFunctions.normalizeAngle(one-two), MathFunctions.normalizeAngle(two-one));
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the direction to turn between a start heading and an end heading. Positive is left
|
||||
* and negative is right. This operates in radians.
|
||||
*
|
||||
* @return returns the turn direction.
|
||||
*/
|
||||
public static double getTurnDirection(double startHeading, double endHeading) {
|
||||
if (MathFunctions.normalizeAngle(endHeading-startHeading) >= 0 && MathFunctions.normalizeAngle(endHeading-startHeading) <= Math.PI) {
|
||||
return 1; // counter clock wise
|
||||
}
|
||||
return -1; // clock wise
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the distance between a Pose and a Point,
|
||||
*
|
||||
* @param pose this is the Pose.
|
||||
* @param point this is the Point.
|
||||
* @return returns the distance between the two.
|
||||
*/
|
||||
public static double distance(Pose pose, Point point) {
|
||||
return Math.sqrt(Math.pow(pose.getX()-point.getX(), 2) + Math.pow(pose.getY()-point.getY(), 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the distance between a Pose and another Pose.
|
||||
*
|
||||
* @param one this is the first Pose.
|
||||
* @param two this is the second Pose.
|
||||
* @return returns the distance between the two.
|
||||
*/
|
||||
public static double distance(Pose one, Pose two) {
|
||||
return Math.sqrt(Math.pow(one.getX()-two.getX(), 2) + Math.pow(one.getY()-two.getY(), 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Point that is the sum of the two input Point.
|
||||
*
|
||||
* @param one the first Point
|
||||
* @param two the second Point
|
||||
* @return returns the sum of the two Points.
|
||||
*/
|
||||
public static Point addPoints(Point one, Point two) {
|
||||
return new Point(one.getX() + two.getX(), one.getY() + two.getY(), Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Pose that is the sum of the two input Pose.
|
||||
*
|
||||
* @param one the first Pose
|
||||
* @param two the second Pose
|
||||
* @return returns the sum of the two Pose.
|
||||
*/
|
||||
public static Pose addPoses(Pose one, Pose two) {
|
||||
return new Pose(one.getX() + two.getX(), one.getY() + two.getY(), one.getHeading() + two.getHeading());
|
||||
}
|
||||
|
||||
/**
|
||||
* This subtracts the second Point from the first Point and returns the result as a Point.
|
||||
* Do note that order matters here.
|
||||
*
|
||||
* @param one the first Point.
|
||||
* @param two the second Point.
|
||||
* @return returns the difference of the two Points.
|
||||
*/
|
||||
public static Point subtractPoints(Point one, Point two) {
|
||||
return new Point(one.getX() - two.getX(), one.getY() - two.getY(), Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This subtracts the second Pose from the first Pose and returns the result as a Pose.
|
||||
* Do note that order matters here.
|
||||
*
|
||||
* @param one the first Pose.
|
||||
* @param two the second Pose.
|
||||
* @return returns the difference of the two Pose.
|
||||
*/
|
||||
public static Pose subtractPoses(Pose one, Pose two) {
|
||||
return new Pose(one.getX() - two.getX(), one.getY() - two.getY(), one.getHeading() - two.getHeading());
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies a Point by a scalar and returns the result as a Point
|
||||
*
|
||||
* @param point the Point being multiplied.
|
||||
* @param scalar the scalar multiplying into the Point.
|
||||
* @return returns the scaled Point.
|
||||
*/
|
||||
public static Point scalarMultiplyPoint(Point point, double scalar) {
|
||||
return new Point(point.getX()*scalar, point.getY()*scalar, Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a Point, but with a different reference location in the memory. So basically a deep
|
||||
* copy.
|
||||
*
|
||||
* @param point the Point to be deep copied.
|
||||
* @return returns the copied Point.
|
||||
*/
|
||||
public static Point copyPoint(Point point) {
|
||||
return new Point(point.getX(), point.getY(), Point.CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a Vector, but with a different reference location in the memory. So basically a deep
|
||||
* copy.
|
||||
*
|
||||
* @param vector Vector to be deep copied.
|
||||
* @return returns the copied Vector.
|
||||
*/
|
||||
public static Vector copyVector(Vector vector) {
|
||||
return new Vector(vector.getMagnitude(), vector.getTheta());
|
||||
}
|
||||
|
||||
/**
|
||||
* This multiplies a Vector by a scalar and returns the result as a Vector.
|
||||
*
|
||||
* @param vector the Vector being multiplied.
|
||||
* @param scalar the scalar multiplying into the Vector.
|
||||
* @return returns the scaled Vector.
|
||||
*/
|
||||
public static Vector scalarMultiplyVector(Vector vector, double scalar) {
|
||||
return new Vector(vector.getMagnitude()*scalar, vector.getTheta());
|
||||
}
|
||||
|
||||
/**
|
||||
* This normalizes a Vector to be of magnitude 1, unless the Vector is the zero Vector.
|
||||
* In that case, it just returns back the zero Vector but with a different memory location.
|
||||
*
|
||||
* @param vector the Vector being normalized.
|
||||
* @return returns the normalized (or zero) Vector.
|
||||
*/
|
||||
public static Vector normalizeVector(Vector vector) {
|
||||
if (vector.getMagnitude() == 0) {
|
||||
return new Vector(0.0, vector.getTheta());
|
||||
} else {
|
||||
return new Vector(vector.getMagnitude()/Math.abs(vector.getMagnitude()), vector.getTheta());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Vector that is the sum of the two input Vectors.
|
||||
*
|
||||
* @param one the first Vector.
|
||||
* @param two the second Vector.
|
||||
* @return returns the sum of the Vectors.
|
||||
*/
|
||||
public static Vector addVectors(Vector one, Vector two) {
|
||||
Vector returnVector = new Vector();
|
||||
returnVector.setOrthogonalComponents(one.getXComponent()+two.getXComponent(), one.getYComponent()+two.getYComponent());
|
||||
return returnVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* This subtracts the second Vector from the first Vector and returns the result as a Vector.
|
||||
* Do note that order matters here.
|
||||
*
|
||||
* @param one the first Vector.
|
||||
* @param two the second Vector.
|
||||
* @return returns the second Vector subtracted from the first Vector.
|
||||
*/
|
||||
public static Vector subtractVectors(Vector one, Vector two) {
|
||||
Vector returnVector = new Vector();
|
||||
returnVector.setOrthogonalComponents(one.getXComponent()-two.getXComponent(), one.getYComponent()-two.getYComponent());
|
||||
return returnVector;
|
||||
}
|
||||
|
||||
/**
|
||||
* This computes the dot product of the two Vectors.
|
||||
*
|
||||
* @param one the first Vector.
|
||||
* @param two the second Vector.
|
||||
* @return returns the dot product of the two Vectors.
|
||||
*/
|
||||
public static double dotProduct(Vector one, Vector two) {
|
||||
return one.getXComponent()*two.getXComponent() + one.getYComponent()*two.getYComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* This computes the first Vector crossed with the second Vector, so a cross product.
|
||||
* Do note that order matters here.
|
||||
*
|
||||
* @param one the first Vector.
|
||||
* @param two the second Vector.
|
||||
* @return returns the cross product of the two Vectors.
|
||||
*/
|
||||
public static double crossProduct(Vector one, Vector two) {
|
||||
return one.getXComponent()*two.getYComponent() - one.getYComponent()*two.getXComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns whether a specified value is within a second specified value by plus/minus a
|
||||
* specified accuracy amount.
|
||||
*
|
||||
* @param one first number specified.
|
||||
* @param two second number specified.
|
||||
* @param accuracy the level of accuracy specified.
|
||||
* @return returns if the two numbers are within the specified accuracy of each other.
|
||||
*/
|
||||
public static boolean roughlyEquals(double one, double two, double accuracy) {
|
||||
return (one < two + accuracy && one > two - accuracy);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns whether a specified number is within a second specified number by plus/minus 0.0001.
|
||||
*
|
||||
* @param one first number specified.
|
||||
* @param two second number specified.
|
||||
* @return returns if a specified number is within plus/minus 0.0001 of the second specified number.
|
||||
*/
|
||||
public static boolean roughlyEquals(double one, double two) {
|
||||
return roughlyEquals(one, two, 0.0001);
|
||||
}
|
||||
}
|
@ -0,0 +1,486 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is the Path class. This class handles containing information on the actual path the Follower
|
||||
* will follow, not just the shape of the path that the BezierCurve class handles. This contains
|
||||
* information on the stop criteria for a Path, the heading interpolation, and deceleration.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/10/2024
|
||||
*/
|
||||
public class Path {
|
||||
private BezierCurve curve;
|
||||
|
||||
private double startHeading;
|
||||
private double endHeading;
|
||||
private double closestPointCurvature;
|
||||
private double closestPointTValue;
|
||||
private double linearInterpolationEndTime;
|
||||
|
||||
private Vector closestPointTangentVector;
|
||||
private Vector closestPointNormalVector;
|
||||
|
||||
private boolean isTangentHeadingInterpolation = true;
|
||||
private boolean followTangentReversed;
|
||||
|
||||
// A multiplier for the zero power acceleration to change the speed the robot decelerates at
|
||||
// the end of paths.
|
||||
// Increasing this will cause the robot to try to decelerate faster, at the risk of overshoots
|
||||
// or localization slippage.
|
||||
// Decreasing this will cause the deceleration at the end of the Path to be slower, making the
|
||||
// robot slower but reducing risk of end-of-path overshoots or localization slippage.
|
||||
// This can be set individually for each Path, but this is the default.
|
||||
private double zeroPowerAccelerationMultiplier = FollowerConstants.zeroPowerAccelerationMultiplier;
|
||||
|
||||
// When the robot is at the end of its current Path or PathChain and the velocity goes
|
||||
// this value, then end the Path. This is in inches/second.
|
||||
// This can be custom set for each Path.
|
||||
private double pathEndVelocityConstraint = FollowerConstants.pathEndVelocityConstraint;
|
||||
|
||||
// When the robot is at the end of its current Path or PathChain and the translational error
|
||||
// goes below this value, then end the Path. This is in inches.
|
||||
// This can be custom set for each Path.
|
||||
private double pathEndTranslationalConstraint = FollowerConstants.pathEndTranslationalConstraint;
|
||||
|
||||
// When the robot is at the end of its current Path or PathChain and the heading error goes
|
||||
// below this value, then end the Path. This is in radians.
|
||||
// This can be custom set for each Path.
|
||||
private double pathEndHeadingConstraint = FollowerConstants.pathEndHeadingConstraint;
|
||||
|
||||
// When the t-value of the closest point to the robot on the Path is greater than this value,
|
||||
// then the Path is considered at its end.
|
||||
// This can be custom set for each Path.
|
||||
private double pathEndTValueConstraint = FollowerConstants.pathEndTValueConstraint;
|
||||
|
||||
// When the Path is considered at its end parametrically, then the Follower has this many
|
||||
// milliseconds to further correct by default.
|
||||
// This can be custom set for each Path.
|
||||
private double pathEndTimeoutConstraint = FollowerConstants.pathEndTimeoutConstraint;
|
||||
|
||||
/**
|
||||
* Creates a new Path from a BezierCurve. The default heading interpolation is tangential.
|
||||
*
|
||||
* @param curve the BezierCurve.
|
||||
*/
|
||||
public Path(BezierCurve curve) {
|
||||
this.curve = curve;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the heading interpolation to linear with a specified start heading and end heading
|
||||
* for the Path. This will interpolate across the entire length of the Path, so there may be
|
||||
* some issues with end heading accuracy and precision if this is used. If a precise end heading
|
||||
* is necessary, then use the setLinearHeadingInterpolation(double startHeading,
|
||||
* double endHeading, double endTime) method.
|
||||
*
|
||||
* @param startHeading The start of the linear heading interpolation.
|
||||
* @param endHeading The end of the linear heading interpolation.
|
||||
* This will be reached at the end of the Path if no end time is specified.
|
||||
*/
|
||||
public void setLinearHeadingInterpolation(double startHeading, double endHeading) {
|
||||
linearInterpolationEndTime = 1;
|
||||
isTangentHeadingInterpolation = false;
|
||||
this.startHeading = startHeading;
|
||||
this.endHeading = endHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the heading interpolation to linear with a specified start heading and end heading
|
||||
* for the Path. This will interpolate from the start of the Path to the specified end time.
|
||||
* This ensures high accuracy and precision than interpolating across the entire Path. However,
|
||||
* interpolating too quickly can cause undesired oscillations and inaccuracies of its own, so
|
||||
* generally interpolating to something like 0.8 of your Path should work best.
|
||||
*
|
||||
* @param startHeading The start of the linear heading interpolation.
|
||||
* @param endHeading The end of the linear heading interpolation.
|
||||
* This will be reached at the end of the Path if no end time is specified.
|
||||
* @param endTime The end time on the Path that the linear heading interpolation will finish.
|
||||
* This value ranges from [0, 1] since Bezier curves are parametric functions.
|
||||
*/
|
||||
public void setLinearHeadingInterpolation(double startHeading, double endHeading, double endTime) {
|
||||
linearInterpolationEndTime = MathFunctions.clamp(endTime, 0.000000001, 1);
|
||||
isTangentHeadingInterpolation = false;
|
||||
this.startHeading = startHeading;
|
||||
this.endHeading = endHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the heading interpolation to maintain a constant heading.
|
||||
*
|
||||
* @param setHeading the constant heading for the Path.
|
||||
*/
|
||||
public void setConstantHeadingInterpolation(double setHeading) {
|
||||
linearInterpolationEndTime = 1;
|
||||
isTangentHeadingInterpolation = false;
|
||||
startHeading = setHeading;
|
||||
endHeading = setHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the closest Point from a specified pose to the BezierCurve with a binary search
|
||||
* that is limited to some specified step limit.
|
||||
*
|
||||
* @param pose the pose.
|
||||
* @param searchStepLimit the binary search step limit.
|
||||
* @return returns the closest Point.
|
||||
*/
|
||||
public Pose getClosestPoint(Pose pose, int searchStepLimit) {
|
||||
double lower = 0;
|
||||
double upper = 1;
|
||||
Point returnPoint;
|
||||
|
||||
// we don't need to calculate the midpoint, so we start off at the 1/4 and 3/4 point
|
||||
for (int i = 0; i < searchStepLimit; i++) {
|
||||
if (MathFunctions.distance(pose, getPoint(lower + 0.25 * (upper-lower))) > MathFunctions.distance(pose, getPoint(lower + 0.75 * (upper-lower)))) {
|
||||
lower += (upper-lower)/2.0;
|
||||
} else {
|
||||
upper -= (upper-lower)/2.0;
|
||||
}
|
||||
}
|
||||
|
||||
closestPointTValue = lower + 0.5 * (upper-lower);
|
||||
|
||||
returnPoint = getPoint(closestPointTValue);
|
||||
|
||||
closestPointTangentVector = curve.getDerivative(closestPointTValue);
|
||||
|
||||
closestPointNormalVector = curve.getApproxSecondDerivative(closestPointTValue);
|
||||
|
||||
closestPointCurvature = curve.getCurvature(closestPointTValue);
|
||||
|
||||
return new Pose(returnPoint.getX(), returnPoint.getY(), getClosestPointHeadingGoal());
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets whether to follow the tangent heading facing away from (reverse) or towards the
|
||||
* tangent. This will also set your heading interpolation to tangential.
|
||||
*
|
||||
* @param set sets tangential heading reversed or not.
|
||||
*/
|
||||
public void setReversed(boolean set) {
|
||||
isTangentHeadingInterpolation = true;
|
||||
followTangentReversed = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the heading interpolation to tangential.
|
||||
*/
|
||||
public void setTangentHeadingInterpolation() {
|
||||
isTangentHeadingInterpolation = true;
|
||||
followTangentReversed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the unit tangent Vector at the end of the BezierCurve.
|
||||
*
|
||||
* @return returns the end tangent Vector.
|
||||
*/
|
||||
public Vector getEndTangent() {
|
||||
return curve.getEndTangent();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the point on the Bezier curve that is specified by the parametric t value. A
|
||||
* Bezier curve is a parametric function that returns points along it with t ranging from [0, 1],
|
||||
* with 0 being the beginning of the curve and 1 being at the end. The Follower will follow
|
||||
* BezierCurves from 0 to 1, in terms of t.
|
||||
*
|
||||
* @param t this is the t value of the parametric curve. t is clamped to be between 0 and 1 inclusive.
|
||||
* @return this returns the point requested.
|
||||
*/
|
||||
public Point getPoint(double t) {
|
||||
return curve.getPoint(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the t-value of the closest Point on the BezierCurve.
|
||||
*
|
||||
* @return returns the closest Point t-value.
|
||||
*/
|
||||
public double getClosestPointTValue() {
|
||||
return closestPointTValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the approximated length of the BezierCurve.
|
||||
*
|
||||
* @return returns the length of the BezierCurve.
|
||||
*/
|
||||
public double length() {
|
||||
return curve.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the curvature of the BezierCurve at a specified t-value.
|
||||
*
|
||||
* @param t the specified t-value.
|
||||
* @return returns the curvature of the BezierCurve at the specified t-value.
|
||||
*/
|
||||
public double getCurvature(double t) {
|
||||
return curve.getCurvature(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the curvature of the BezierCurve at the closest Point.
|
||||
*
|
||||
* @return returns the curvature of the BezierCurve at the closest Point.
|
||||
*/
|
||||
public double getClosestPointCurvature() {
|
||||
return closestPointCurvature;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the normal Vector at the closest Point.
|
||||
*
|
||||
* @return returns the normal Vector at the closest Point.
|
||||
*/
|
||||
public Vector getClosestPointNormalVector() {
|
||||
return MathFunctions.copyVector(closestPointNormalVector);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the tangent Vector at the closest Point.
|
||||
*
|
||||
* @return returns the tangent Vector at the closest Point.
|
||||
*/
|
||||
public Vector getClosestPointTangentVector() {
|
||||
return MathFunctions.copyVector(closestPointTangentVector);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the heading goal at the closest Point.
|
||||
*
|
||||
* @return returns the heading goal at the closest Point.
|
||||
*/
|
||||
public double getClosestPointHeadingGoal() {
|
||||
if (isTangentHeadingInterpolation) {
|
||||
if (followTangentReversed) return MathFunctions.normalizeAngle(closestPointTangentVector.getTheta() + Math.PI);
|
||||
return closestPointTangentVector.getTheta();
|
||||
} else {
|
||||
return getHeadingGoal(closestPointTValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the heading goal at a specified t-value.
|
||||
*
|
||||
* @param t the specified t-value.
|
||||
* @return returns the heading goal at the specified t-value.
|
||||
*/
|
||||
public double getHeadingGoal(double t) {
|
||||
if (isTangentHeadingInterpolation) {
|
||||
if (followTangentReversed) return MathFunctions.normalizeAngle(curve.getDerivative(t).getTheta() + Math.PI);
|
||||
return curve.getDerivative(t).getTheta();
|
||||
} else {
|
||||
if (t > linearInterpolationEndTime) {
|
||||
return MathFunctions.normalizeAngle(endHeading);
|
||||
}
|
||||
return MathFunctions.normalizeAngle(startHeading + MathFunctions.getTurnDirection(startHeading, endHeading) * MathFunctions.getSmallestAngleDifference(endHeading, startHeading) * (t / linearInterpolationEndTime));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns if the robot is at the end of the Path, according to the parametric t-value.
|
||||
*
|
||||
* @return returns if at end.
|
||||
*/
|
||||
public boolean isAtParametricEnd() {
|
||||
if (closestPointTValue >= pathEndTValueConstraint) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns if the robot is at the beginning of the Path, according to the parametric t-value.
|
||||
*
|
||||
* @return returns if at start.
|
||||
*/
|
||||
public boolean isAtParametricStart() {
|
||||
if (closestPointTValue <= 1- pathEndTValueConstraint) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ArrayList of control points for this BezierCurve.
|
||||
*
|
||||
* @return This returns the control points.
|
||||
*/
|
||||
public ArrayList<Point> getControlPoints() {
|
||||
return curve.getControlPoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first control point for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getFirstControlPoint() {
|
||||
return curve.getFirstControlPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second control point, or the one after the start, for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getSecondControlPoint() {
|
||||
return curve.getSecondControlPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second to last control point for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getSecondToLastControlPoint() {
|
||||
return curve.getSecondToLastControlPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last control point for this BezierCurve.
|
||||
*
|
||||
* @return This returns the Point.
|
||||
*/
|
||||
public Point getLastControlPoint() {
|
||||
return curve.getLastControlPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the path's deceleration factor in terms of the natural deceleration of the robot
|
||||
* when power is cut from the drivetrain.
|
||||
*
|
||||
* @param set This sets the multiplier.
|
||||
*/
|
||||
public void setZeroPowerAccelerationMultiplier(double set) {
|
||||
zeroPowerAccelerationMultiplier = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the velocity stop criteria. When velocity is below this amount, then this is met.
|
||||
*
|
||||
* @param set This sets the velocity end constraint.
|
||||
*/
|
||||
public void setPathEndVelocityConstraint(double set) {
|
||||
pathEndVelocityConstraint = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the translational stop criteria. When the translational error, or how far off the
|
||||
* end point the robot is, goes below this, then the translational end criteria is met.
|
||||
*
|
||||
* @param set This sets the translational end constraint.
|
||||
*/
|
||||
public void setPathEndTranslationalConstraint(double set) {
|
||||
pathEndTranslationalConstraint = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the heading stop criteria. When the heading error is less than this amount, then
|
||||
* the heading end criteria is met.
|
||||
*
|
||||
* @param set This sets the heading end constraint.
|
||||
*/
|
||||
public void setPathEndHeadingConstraint(double set) {
|
||||
pathEndHeadingConstraint = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the parametric end criteria. When the t-value of the closest Point on the Path is
|
||||
* greater than this amount, then the parametric end criteria is met.
|
||||
*
|
||||
* @param set This sets the t-value end constraint.
|
||||
*/
|
||||
public void setPathEndTValueConstraint(double set) {
|
||||
pathEndTValueConstraint = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the Path end timeout. If the Path is at its end parametrically, then the Follower
|
||||
* has this many milliseconds to correct before the Path gets ended anyways.
|
||||
*
|
||||
* @param set This sets the Path end timeout.
|
||||
*/
|
||||
public void setPathEndTimeoutConstraint(double set) {
|
||||
pathEndTimeoutConstraint = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the deceleration multiplier.
|
||||
*
|
||||
* @return This returns the deceleration multiplier.
|
||||
*/
|
||||
public double getZeroPowerAccelerationMultiplier() {
|
||||
return zeroPowerAccelerationMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the velocity stop criteria.
|
||||
*
|
||||
* @return This returns the velocity stop criteria.
|
||||
*/
|
||||
public double getPathEndVelocityConstraint() {
|
||||
return pathEndVelocityConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the translational stop criteria.
|
||||
*
|
||||
* @return This returns the translational stop criteria.
|
||||
*/
|
||||
public double getPathEndTranslationalConstraint() {
|
||||
return pathEndTranslationalConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the heading stop criteria.
|
||||
*
|
||||
* @return This returns the heading stop criteria.
|
||||
*/
|
||||
public double getPathEndHeadingConstraint() {
|
||||
return pathEndHeadingConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the parametric end criteria.
|
||||
*
|
||||
* @return This returns the parametric end criteria.
|
||||
*/
|
||||
public double getPathEndTValueConstraint() {
|
||||
return pathEndTValueConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets the Path end correction time.
|
||||
*
|
||||
* @return This returns the Path end correction time.
|
||||
*/
|
||||
public double getPathEndTimeoutConstraint() {
|
||||
return pathEndTimeoutConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of path. This is used in case we need to identify the type of BezierCurve
|
||||
* this is.
|
||||
*
|
||||
* @return returns the type of path.
|
||||
*/
|
||||
public String pathType() {
|
||||
return curve.pathType();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a 2D Array of doubles containing the x and y positions of points to draw on FTC
|
||||
* Dashboard.
|
||||
*
|
||||
* @return returns the 2D Array to draw on FTC Dashboard
|
||||
*/
|
||||
public double[][] getDashboardDrawingPoints() {
|
||||
return curve.getDashboardDrawingPoints();
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is the PathBuilder class. This class makes it easier to create PathChains, so you don't have
|
||||
* to individually create Path instances to create a PathChain. A PathBuilder can be accessed
|
||||
* through running the pathBuilder() method on an instance of the Follower class, or just creating
|
||||
* an instance of PathBuilder regularly.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/11/2024
|
||||
*/
|
||||
public class PathBuilder {
|
||||
private ArrayList<Path> paths = new ArrayList<>();
|
||||
|
||||
private ArrayList<PathCallback> callbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* This is an empty constructor for the PathBuilder class so it can get started.
|
||||
* The PathBuilder allows for easier construction of PathChains.
|
||||
* The proper usage is using an instance of the Follower class:
|
||||
* Follower follower = new Follower(hardwareMap);
|
||||
* Then calling "follower.pathBuilder.[INSERT PATH BUILDING METHODS].build();
|
||||
* Of course, you can split up the method calls onto separate lines for readability.
|
||||
*/
|
||||
public PathBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds a Path to the PathBuilder.
|
||||
*
|
||||
* @param path The Path being added.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder addPath(Path path) {
|
||||
this.paths.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds a default Path defined by a specified BezierCurve to the PathBuilder.
|
||||
*
|
||||
* @param curve The curve is turned into a Path and added.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder addPath(BezierCurve curve) {
|
||||
this.paths.add(new Path(curve));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a linear heading interpolation on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param startHeading The start of the linear heading interpolation.
|
||||
* @param endHeading The end of the linear heading interpolation.
|
||||
* This will be reached at the end of the Path if no end time is specified.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setLinearHeadingInterpolation(double startHeading, double endHeading) {
|
||||
this.paths.get(paths.size()-1).setLinearHeadingInterpolation(startHeading, endHeading);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a linear heading interpolation on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param startHeading The start of the linear heading interpolation.
|
||||
* @param endHeading The end of the linear heading interpolation.
|
||||
* This will be reached at the end of the Path if no end time is specified.
|
||||
* @param endTime The end time on the Path that the linear heading interpolation will end.
|
||||
* This value goes from [0, 1] since Bezier curves are parametric functions.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setLinearHeadingInterpolation(double startHeading, double endHeading, double endTime) {
|
||||
this.paths.get(paths.size()-1).setLinearHeadingInterpolation(startHeading, endHeading, endTime);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a constant heading interpolation on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param setHeading The constant heading specified.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setConstantHeadingInterpolation(double setHeading) {
|
||||
this.paths.get(paths.size()-1).setConstantHeadingInterpolation(setHeading);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a reversed or tangent heading interpolation on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the heading to reversed tangent following if this parameter is true.
|
||||
* Conversely, this sets a tangent following if this parameter is false.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setReversed(boolean set) {
|
||||
this.paths.get(paths.size()-1).setReversed(set);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* This sets the heading interpolation to tangential on the last Path added to the PathBuilder.
|
||||
* There really shouldn't be a reason to use this since the default heading interpolation is
|
||||
* tangential but it's here.
|
||||
*/
|
||||
public PathBuilder setTangentHeadingInterpolation() {
|
||||
this.paths.get(paths.size()-1).setTangentHeadingInterpolation();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the deceleration multiplier on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the multiplier for the goal for the deceleration of the robot.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setZeroPowerAccelerationMultiplier(double set) {
|
||||
this.paths.get(paths.size()-1).setZeroPowerAccelerationMultiplier(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the path end velocity constraint on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the path end velocity constraint.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setPathEndVelocityConstraint(double set) {
|
||||
this.paths.get(paths.size()-1).setPathEndVelocityConstraint(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the path end translational constraint on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the path end translational constraint.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setPathEndTranslationalConstraint(double set) {
|
||||
this.paths.get(paths.size()-1).setPathEndTranslationalConstraint(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the path end heading constraint on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the path end heading constraint.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setPathEndHeadingConstraint(double set) {
|
||||
this.paths.get(paths.size()-1).setPathEndHeadingConstraint(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the path end t-value (parametric time) constraint on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the path end t-value (parametric time) constraint.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setPathEndTValueConstraint(double set) {
|
||||
this.paths.get(paths.size()-1).setPathEndTValueConstraint(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the path end timeout constraint on the last Path added to the PathBuilder.
|
||||
*
|
||||
* @param set This sets the path end timeout constraint.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder setPathEndTimeoutConstraint(double set) {
|
||||
this.paths.get(paths.size()-1).setPathEndTimeoutConstraint(set);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds a temporal callback on the last Path added to the PathBuilder.
|
||||
* This callback is set to run at a specified number of milliseconds after the start of the path.
|
||||
*
|
||||
* @param time This sets the number of milliseconds of wait between the start of the Path and
|
||||
* the calling of the callback.
|
||||
* @param runnable This sets the code for the callback to run. Use lambda statements for this.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder addTemporalCallback(double time, Runnable runnable) {
|
||||
this.callbacks.add(new PathCallback(time, runnable, PathCallback.TIME, paths.size()-1));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds a parametric callback on the last Path added to the PathBuilder.
|
||||
* This callback is set to run at a certain point on the Path.
|
||||
*
|
||||
* @param t This sets the t-value (parametric time) on the Path for when to run the callback.
|
||||
* @param runnable This sets the code for the callback to run. Use lambda statements for this.
|
||||
* @return This returns itself with the updated data.
|
||||
*/
|
||||
public PathBuilder addParametricCallback(double t, Runnable runnable) {
|
||||
this.callbacks.add(new PathCallback(t, runnable, PathCallback.PARAMETRIC, paths.size()-1));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This builds all the Path and callback information together into a PathChain.
|
||||
*
|
||||
* @return This returns a PathChain made of all the specified paths and callbacks.
|
||||
*/
|
||||
public PathChain build() {
|
||||
PathChain returnChain = new PathChain(paths);
|
||||
returnChain.setCallbacks(callbacks);
|
||||
return returnChain;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.SingleRunAction;
|
||||
|
||||
/**
|
||||
* This is the PathCallback class. This class handles callbacks of Runnables in PathChains.
|
||||
* Basically, this allows you to run non-blocking code in the middle of PathChains.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/11/2024
|
||||
*/
|
||||
public class PathCallback extends SingleRunAction {
|
||||
|
||||
private double startCondition;
|
||||
|
||||
private int type;
|
||||
private int index;
|
||||
|
||||
public static final int TIME = 0;
|
||||
public static final int PARAMETRIC = 1;
|
||||
|
||||
/**
|
||||
* This creates a new PathCallback with a specified start condition (either time or parametric),
|
||||
* a Runnable of code to run (preferably a lambda statement), a type (using the class constants),
|
||||
* and an index for which Path within a PathChain the callback is to run on.
|
||||
*
|
||||
* @param startCondition This defines when the callback is to be run, either as a wait time in
|
||||
* milliseconds or a t-value (parametric time) point.
|
||||
* @param runnable This contains the code to run when the callback is called.
|
||||
* @param type This defines the type of callback using the class constants.
|
||||
* @param index This defines which Path within the PathChain the callback is to run on.
|
||||
*/
|
||||
public PathCallback(double startCondition, Runnable runnable, int type, int index) {
|
||||
super(runnable);
|
||||
this.startCondition = startCondition;
|
||||
this.type = type;
|
||||
if (this.type != TIME || this.type != PARAMETRIC) {
|
||||
this.type = PARAMETRIC;
|
||||
}
|
||||
if (this.type == TIME && this.startCondition < 0) {
|
||||
this.startCondition = 0.0;
|
||||
}
|
||||
if (this.type == PARAMETRIC) {
|
||||
this.startCondition = MathFunctions.clamp(this.startCondition, 0, 1);
|
||||
}
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the type of callback this is (time or parametric).
|
||||
*
|
||||
* @return This returns the type of callback.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the start condition for this callback. This will be the wait time in milliseconds
|
||||
* if this is a time callback or a t-value if this is a parametric callback.
|
||||
*
|
||||
* @return This returns the start condition.
|
||||
*/
|
||||
public double getStartCondition() {
|
||||
return startCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the index of which Path the callback is to run on within the PathChain.
|
||||
*
|
||||
* @return This returns the Path index of this callback.
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is the PathChain class. This class handles chaining together multiple Paths into a larger
|
||||
* collection of Paths that can be run continuously. Additionally, this allows for the addition of
|
||||
* PathCallbacks to specific Paths in the PathChain, allowing for non-blocking code to be run in
|
||||
* the middle of a PathChain.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/11/2024
|
||||
*/
|
||||
public class PathChain {
|
||||
private ArrayList<Path> pathChain = new ArrayList<>();
|
||||
|
||||
private ArrayList<PathCallback> callbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* This creates a new PathChain from some specified Paths.
|
||||
*
|
||||
* IMPORTANT NOTE: Order matters here. The order in which the Paths are input is the order in
|
||||
* which they will be run.
|
||||
*
|
||||
* @param paths the specified Paths.
|
||||
*/
|
||||
public PathChain(Path... paths) {
|
||||
for (Path path : paths) {
|
||||
pathChain.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new PathChain from an ArrayList of Paths.
|
||||
*
|
||||
* IMPORTANT NOTE: Order matters here. The order in which the Paths are input is the order in
|
||||
* which they will be run.
|
||||
*
|
||||
* @param paths the ArrayList of Paths.
|
||||
*/
|
||||
public PathChain(ArrayList<Path> paths) {
|
||||
pathChain = paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the Path on the PathChain at a specified index.
|
||||
*
|
||||
* @param index the index.
|
||||
* @return returns the Path at the index.
|
||||
*/
|
||||
public Path getPath(int index) {
|
||||
return pathChain.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the size of the PathChain.
|
||||
*
|
||||
* @return returns the size of the PathChain.
|
||||
*/
|
||||
public int size() {
|
||||
return pathChain.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the PathCallbacks of the PathChain with some specified PathCallbacks.
|
||||
*
|
||||
* @param callbacks the specified PathCallbacks.
|
||||
*/
|
||||
public void setCallbacks(PathCallback... callbacks) {
|
||||
for (PathCallback callback : callbacks) {
|
||||
this.callbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the PathCallbacks of the PathChain with an ArrayList of PathCallbacks.
|
||||
*
|
||||
* @param callbacks the ArrayList of PathCallbacks.
|
||||
*/
|
||||
public void setCallbacks(ArrayList<PathCallback> callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the PathCallbacks of this PathChain in an ArrayList.
|
||||
*
|
||||
* @return returns the PathCallbacks.
|
||||
*/
|
||||
public ArrayList<PathCallback> getCallbacks() {
|
||||
return callbacks;
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
|
||||
/**
|
||||
* This is the Point class. This class handles storing information about the location of points in
|
||||
* 2D space in both Cartesian, or rectangular, and polar coordinates. Additionally, this contains
|
||||
* the method to find the distance between two Points.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/11/2024
|
||||
*/
|
||||
public class Point {
|
||||
|
||||
// IMPORTANT NOTE: theta is defined in radians.
|
||||
// These are the values of the coordinate defined by this Point, in both polar and
|
||||
// Cartesian systems.
|
||||
private double r;
|
||||
private double theta;
|
||||
private double x;
|
||||
private double y;
|
||||
|
||||
// these are used for ease of changing/setting identification
|
||||
public static final int POLAR = 0;
|
||||
public static final int CARTESIAN = 1;
|
||||
|
||||
|
||||
/**
|
||||
* This creates a new Point with coordinate inputs and a specified coordinate system.
|
||||
*
|
||||
* @param rOrX Depending on the coordinate system specified, this is either the r or x value.
|
||||
* In polar coordinates, the r value is the distance from the origin.
|
||||
* In Cartesian coordinates, the x value is the distance left/right from the origin.
|
||||
* @param thetaOrY Depending on the coordinate system specified, this is either the theta or
|
||||
* y value.
|
||||
* In polar coordinates, the theta value is the angle from the positive x-axis.
|
||||
* Increasing theta moves in the counter-clockwise direction.
|
||||
* In Cartesian coordinates, the y value is the distance up/down from the origin.
|
||||
* @param identifier this specifies what coordinate system the coordinate inputs are in.
|
||||
*/
|
||||
public Point(double rOrX, double thetaOrY, int identifier) {
|
||||
setCoordinates(rOrX, thetaOrY, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Point from a Pose.
|
||||
*
|
||||
* @param pose the Pose.
|
||||
*/
|
||||
public Point(Pose pose) {
|
||||
setCoordinates(pose.getX(), pose.getY(), CARTESIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the coordinates of the Point using the specified coordinate system.
|
||||
*
|
||||
* @param rOrX Depending on the coordinate system specified, this is either the r or x value.
|
||||
* In polar coordinates, the r value is the distance from the origin.
|
||||
* In Cartesian coordinates, the x value is the distance left/right from the origin.
|
||||
* @param thetaOrY Depending on the coordinate system specified, this is either the theta or
|
||||
* y value.
|
||||
* In polar coordinates, the theta value is the angle from the positive x-axis.
|
||||
* Increasing theta moves in the counter-clockwise direction.
|
||||
* In Cartesian coordinates, the y value is the distance up/down from the origin.
|
||||
* @param identifier this specifies what coordinate system to use when setting values.
|
||||
*/
|
||||
public void setCoordinates(double rOrX, double thetaOrY, int identifier) {
|
||||
double[] setOtherCoordinates;
|
||||
switch (identifier) { // this detects which coordinate system to use
|
||||
// there is no POLAR case since that's covered by the default
|
||||
case CARTESIAN:
|
||||
x = rOrX;
|
||||
y = thetaOrY;
|
||||
setOtherCoordinates = cartesianToPolar(x, y);
|
||||
r = setOtherCoordinates[0];
|
||||
theta = setOtherCoordinates[1];
|
||||
break;
|
||||
default:
|
||||
if (rOrX<0) {
|
||||
r = -rOrX;
|
||||
theta = MathFunctions.normalizeAngle(thetaOrY+Math.PI);
|
||||
} else {
|
||||
r = rOrX;
|
||||
theta = MathFunctions.normalizeAngle(thetaOrY);
|
||||
}
|
||||
setOtherCoordinates = polarToCartesian(r, theta);
|
||||
x = setOtherCoordinates[0];
|
||||
y = setOtherCoordinates[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the distance between this Point and some other specified Point.
|
||||
*
|
||||
* @param otherPoint the other specified Point.
|
||||
* @return returns the distance between the two Points.
|
||||
*/
|
||||
public double distanceFrom(Point otherPoint) {
|
||||
return Math.sqrt(Math.pow(otherPoint.getX()-x, 2) + Math.pow(otherPoint.getY()-y, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes in an r and theta value and converts them to Cartesian coordinates.
|
||||
*
|
||||
* @param r this is the r value of the Point being converted.
|
||||
* @param theta this is the theta value of the Point being converted.
|
||||
* @return this returns the x and y values, in that order, in an Array of doubles.
|
||||
*/
|
||||
public static double[] polarToCartesian(double r, double theta) {
|
||||
return new double[] {r * Math.cos(theta), r * Math.sin(theta)};
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes in an x and y value and converts them to polar coordinates.
|
||||
*
|
||||
* @param x this is the x value of the Point being converted.
|
||||
* @param y this is the y value of the Point being converted.
|
||||
* @return this returns the r and theta values, in that order, in an Array of doubles.
|
||||
*/
|
||||
public static double[] cartesianToPolar(double x, double y) {
|
||||
if (x == 0) {
|
||||
if (y > 0) {
|
||||
return new double[] {Math.abs(y), Math.PI/2};
|
||||
} else {
|
||||
return new double[] {Math.abs(y), (3 * Math.PI) / 2};
|
||||
}
|
||||
}
|
||||
double r = Math.sqrt(x*x+y*y);
|
||||
if (x < 0) return new double[] {r, Math.PI+Math.atan(y/x)};
|
||||
if (y > 0) {
|
||||
return new double[]{r, Math.atan(y / x)};
|
||||
} else {
|
||||
return new double[]{r, (2*Math.PI) + Math.atan(y / x)};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the r value of this Point. This is a polar coordinate value.
|
||||
*
|
||||
* @return returns the r value.
|
||||
*/
|
||||
public double getR() {
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theta value of this Point. This is a polar coordinate value.
|
||||
*
|
||||
* @return returns the theta value.
|
||||
*/
|
||||
public double getTheta() {
|
||||
return theta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x value of this Point. This is a Cartesian coordinate value.
|
||||
*
|
||||
* @return returns the x value.
|
||||
*/
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y value of this Point. This is a Cartesian coordinate value.
|
||||
*
|
||||
* @return returns the y value.
|
||||
*/
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Point with the same information as this Point, just pointing to a different
|
||||
* memory location. In other words, a deep copy.
|
||||
*
|
||||
* @return returns a copy of this Point.
|
||||
*/
|
||||
public Point copy() {
|
||||
return new Point(getX(), getY(), CARTESIAN);
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration;
|
||||
|
||||
/**
|
||||
* This is the Point class. This class handles storing information about vectors, which are
|
||||
* basically Points but using polar coordinates as the default. The main reason this class exists
|
||||
* is because some vector math needs to be done in the Follower, and dot products and cross
|
||||
* products of Points just don't seem right. Also, there are a few more methods in here that make
|
||||
* using Vectors a little easier than using a Point in polar coordinates.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/11/2024
|
||||
*/
|
||||
public class Vector {
|
||||
|
||||
// IMPORTANT NOTE: theta is defined in radians.
|
||||
// These are the values of the coordinate defined by this Point, in both polar and
|
||||
// Cartesian systems.
|
||||
private double magnitude;
|
||||
private double theta;
|
||||
private double xComponent;
|
||||
private double yComponent;
|
||||
|
||||
/**
|
||||
* This creates a new Vector with zero magnitude and direction.
|
||||
*/
|
||||
public Vector() {
|
||||
setComponents(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new Vector with a specified magnitude and direction.
|
||||
*
|
||||
* @param magnitude magnitude of the Vector.
|
||||
* @param theta the direction of the Vector in radians.
|
||||
*/
|
||||
public Vector(double magnitude, double theta) {
|
||||
setComponents(magnitude, theta);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the components of the Vector in regular vector coordinates.
|
||||
*
|
||||
* @param magnitude sets the magnitude of this Vector.
|
||||
* @param theta sets the theta value of this Vector.
|
||||
*/
|
||||
public void setComponents(double magnitude, double theta) {
|
||||
double[] orthogonalComponents;
|
||||
if (magnitude<0) {
|
||||
this.magnitude = -magnitude;
|
||||
this.theta = MathFunctions.normalizeAngle(theta+Math.PI);
|
||||
} else {
|
||||
this.magnitude = magnitude;
|
||||
this.theta = MathFunctions.normalizeAngle(theta);
|
||||
}
|
||||
orthogonalComponents = Point.polarToCartesian(magnitude, theta);
|
||||
xComponent = orthogonalComponents[0];
|
||||
yComponent = orthogonalComponents[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets only the magnitude of the Vector.
|
||||
*
|
||||
* @param magnitude sets the magnitude of this Vector.
|
||||
*/
|
||||
public void setMagnitude(double magnitude) {
|
||||
setComponents(magnitude, theta);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets only the angle, theta, of the Vector.
|
||||
*
|
||||
* @param theta sets the angle, or theta value, of this Vector.
|
||||
*/
|
||||
public void setTheta(double theta) {
|
||||
setComponents(magnitude, theta);
|
||||
}
|
||||
|
||||
/**
|
||||
* This rotates the Vector by an angle, theta.
|
||||
*
|
||||
* @param theta2 the angle to be added.
|
||||
*/
|
||||
public void rotateVector(double theta2) {
|
||||
setTheta(theta+theta2);
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the orthogonal components of the Vector. These orthogonal components are assumed
|
||||
* to be in the direction of the x-axis and y-axis. In other words, this is setting the
|
||||
* coordinates of the Vector using x and y coordinates instead of a direction and magnitude.
|
||||
*
|
||||
* @param xComponent sets the x component of this Vector.
|
||||
* @param yComponent sets the y component of this Vector.
|
||||
*/
|
||||
public void setOrthogonalComponents(double xComponent, double yComponent) {
|
||||
double[] polarComponents;
|
||||
this.xComponent = xComponent;
|
||||
this.yComponent = yComponent;
|
||||
polarComponents = Point.cartesianToPolar(xComponent, yComponent);
|
||||
magnitude = polarComponents[0];
|
||||
theta = polarComponents[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the magnitude of this Vector.
|
||||
*
|
||||
* @return returns the magnitude.
|
||||
*/
|
||||
public double getMagnitude() {
|
||||
return magnitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theta value, or angle, of this Vector.
|
||||
*
|
||||
* @return returns the theta value.
|
||||
*/
|
||||
public double getTheta() {
|
||||
return theta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x component of this Vector.
|
||||
*
|
||||
* @return returns the x component.
|
||||
*/
|
||||
public double getXComponent() {
|
||||
return xComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y component of this Vector.
|
||||
*
|
||||
* @return returns the y component.
|
||||
*/
|
||||
public double getYComponent() {
|
||||
return yComponent;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.follower.Follower;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.BezierCurve;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.PathChain;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Point;
|
||||
|
||||
/**
|
||||
* This is the Circle autonomous OpMode. It runs the robot in a PathChain that's actually not quite
|
||||
* a circle, but some Bezier curves that have control points set essentially in a square. However,
|
||||
* it turns enough to tune your centripetal force correction and some of your heading. Some lag in
|
||||
* heading is to be expected.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/12/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Circle", group = "Autonomous Pathing Tuning")
|
||||
public class Circle extends OpMode {
|
||||
private Telemetry telemetryA;
|
||||
|
||||
public static double RADIUS = 10;
|
||||
|
||||
private Follower follower;
|
||||
|
||||
private PathChain circle;
|
||||
|
||||
/**
|
||||
* This initializes the Follower and creates the PathChain for the "circle". Additionally, this
|
||||
* initializes the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
follower = new Follower(hardwareMap);
|
||||
|
||||
circle = follower.pathBuilder()
|
||||
.addPath(new BezierCurve(new Point(0,0, Point.CARTESIAN), new Point(RADIUS,0, Point.CARTESIAN), new Point(RADIUS, RADIUS, Point.CARTESIAN)))
|
||||
.addPath(new BezierCurve(new Point(RADIUS, RADIUS, Point.CARTESIAN), new Point(RADIUS,2*RADIUS, Point.CARTESIAN), new Point(0,2*RADIUS, Point.CARTESIAN)))
|
||||
.addPath(new BezierCurve(new Point(0,2*RADIUS, Point.CARTESIAN), new Point(-RADIUS,2*RADIUS, Point.CARTESIAN), new Point(-RADIUS, RADIUS, Point.CARTESIAN)))
|
||||
.addPath(new BezierCurve(new Point(-RADIUS, RADIUS, Point.CARTESIAN), new Point(-RADIUS,0, Point.CARTESIAN), new Point(0,0, Point.CARTESIAN)))
|
||||
.build();
|
||||
|
||||
follower.followPath(circle);
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("This will run in a roughly circular shape of radius " + RADIUS
|
||||
+ ", starting on the right-most edge. So, make sure you have enough "
|
||||
+ "space to the left, front, and back to run the OpMode.");
|
||||
telemetryA.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode, updating the Follower as well as printing out the debug statements to
|
||||
* the Telemetry, as well as the FTC Dashboard.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
follower.update();
|
||||
if (follower.atParametricEnd()) {
|
||||
follower.followPath(circle);
|
||||
}
|
||||
|
||||
follower.telemetryDebug(telemetryA);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.follower.Follower;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.BezierCurve;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Path;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Point;
|
||||
|
||||
/**
|
||||
* This is the CurvedBackAndForth autonomous OpMode. It runs the robot in a specified distance
|
||||
* forward and to the left. On reaching the end of the forward Path, the robot runs the backward
|
||||
* Path the same distance back to the start. Rinse and repeat! This is good for testing a variety
|
||||
* of Vectors, like the drive Vector, the translational Vector, the heading Vector, and the
|
||||
* centripetal Vector. Remember to test your tunings on StraightBackAndForth as well, since tunings
|
||||
* that work well for curves might have issues going in straight lines.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/13/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Curved Back And Forth", group = "Autonomous Pathing Tuning")
|
||||
public class CurvedBackAndForth extends OpMode {
|
||||
private Telemetry telemetryA;
|
||||
|
||||
public static double DISTANCE = 20;
|
||||
|
||||
private boolean forward = true;
|
||||
|
||||
private Follower follower;
|
||||
|
||||
private Path forwards;
|
||||
private Path backwards;
|
||||
|
||||
/**
|
||||
* This initializes the Follower and creates the forward and backward Paths. Additionally, this
|
||||
* initializes the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
follower = new Follower(hardwareMap);
|
||||
|
||||
forwards = new Path(new BezierCurve(new Point(0,0, Point.CARTESIAN), new Point(Math.abs(DISTANCE),0, Point.CARTESIAN), new Point(Math.abs(DISTANCE),DISTANCE, Point.CARTESIAN)));
|
||||
backwards = new Path(new BezierCurve(new Point(Math.abs(DISTANCE),DISTANCE, Point.CARTESIAN), new Point(Math.abs(DISTANCE),0, Point.CARTESIAN), new Point(0,0, Point.CARTESIAN)));
|
||||
|
||||
backwards.setReversed(true);
|
||||
|
||||
follower.followPath(forwards);
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("This will run the robot in a curve going " + DISTANCE + " inches"
|
||||
+ " to the left and the same number of inches forward. The robot will go"
|
||||
+ "forward and backward continuously along the path. Make sure you have"
|
||||
+ "enough room.");
|
||||
telemetryA.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode, updating the Follower as well as printing out the debug statements to
|
||||
* the Telemetry, as well as the FTC Dashboard.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
follower.update();
|
||||
if (!follower.isBusy()) {
|
||||
if (forward) {
|
||||
forward = false;
|
||||
follower.followPath(backwards);
|
||||
} else {
|
||||
forward = true;
|
||||
follower.followPath(forwards);
|
||||
}
|
||||
}
|
||||
|
||||
telemetryA.addData("going forward", forward);
|
||||
follower.telemetryDebug(telemetryA);
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.PedroConstants.*;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.CustomFilteredPIDFCoefficients;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.CustomPIDFCoefficients;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Point;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.util.KalmanFilterParameters;
|
||||
|
||||
/**
|
||||
* This is the FollowerConstants class. It holds many constants and parameters for various parts of
|
||||
* the Follower. This is here to allow for easier tuning of Pedro Pathing, as well as concentrate
|
||||
* everything tunable for the Paths themselves in one place.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/4/2024
|
||||
*/
|
||||
@Config
|
||||
public class FollowerConstants {
|
||||
|
||||
// This section is for configuring your motors
|
||||
public static String leftFrontMotorName = FRONT_LEFT_MOTOR;
|
||||
public static String leftRearMotorName = BACK_LEFT_MOTOR;
|
||||
public static String rightFrontMotorName = FRONT_RIGHT_MOTOR;
|
||||
public static String rightRearMotorName = BACK_RIGHT_MOTOR;
|
||||
|
||||
// This section is for setting the actual drive vector for the front left wheel, if the robot
|
||||
// is facing a heading of 0 radians with the wheel centered at (0,0)
|
||||
private static final double xMovement = ROBOT_SPEED_FORWARD;
|
||||
private static final double yMovement = ROBOT_SPEED_LATERAL;
|
||||
private static final double[] convertToPolar = Point.cartesianToPolar(xMovement, -yMovement);
|
||||
public static Vector frontLeftVector = MathFunctions.normalizeVector(new Vector(convertToPolar[0],convertToPolar[1]));
|
||||
|
||||
|
||||
// Translational PIDF coefficients (don't use integral)
|
||||
public static CustomPIDFCoefficients translationalPIDFCoefficients = new CustomPIDFCoefficients(
|
||||
0.1,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Translational Integral
|
||||
public static CustomPIDFCoefficients translationalIntegral = new CustomPIDFCoefficients(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Feed forward constant added on to the translational PIDF
|
||||
public static double translationalPIDFFeedForward = 0.015;
|
||||
|
||||
|
||||
// Heading error PIDF coefficients
|
||||
public static CustomPIDFCoefficients headingPIDFCoefficients = new CustomPIDFCoefficients(
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Feed forward constant added on to the heading PIDF
|
||||
public static double headingPIDFFeedForward = 0.01;
|
||||
|
||||
|
||||
// Drive PIDF coefficients
|
||||
public static CustomFilteredPIDFCoefficients drivePIDFCoefficients = new CustomFilteredPIDFCoefficients(
|
||||
0.025,
|
||||
0,
|
||||
0.00001,
|
||||
0.6,
|
||||
0);
|
||||
|
||||
// Feed forward constant added on to the drive PIDF
|
||||
public static double drivePIDFFeedForward = 0.01;
|
||||
|
||||
// Kalman filter parameters for the drive error Kalman filter
|
||||
public static KalmanFilterParameters driveKalmanFilterParameters = new KalmanFilterParameters(
|
||||
6,
|
||||
1);
|
||||
|
||||
|
||||
// Mass of robot in kilograms
|
||||
public static double mass = ROBOT_WEIGHT_IN_KG;
|
||||
|
||||
// Centripetal force to power scaling
|
||||
public static double centripetalScaling = CENTRIPETAL_SCALING;
|
||||
|
||||
|
||||
// Acceleration of the drivetrain when power is cut in inches/second^2 (should be negative)
|
||||
// if not negative, then the robot thinks that its going to go faster under 0 power
|
||||
public static double forwardZeroPowerAcceleration = FORWARD_ZERO_POWER_ACCEL;
|
||||
|
||||
// Acceleration of the drivetrain when power is cut in inches/second^2 (should be negative)
|
||||
// if not negative, then the robot thinks that its going to go faster under 0 power
|
||||
public static double lateralZeroPowerAcceleration = LATERAL_ZERO_POWER_ACCEL;
|
||||
|
||||
// A multiplier for the zero power acceleration to change the speed the robot decelerates at
|
||||
// the end of paths.
|
||||
// Increasing this will cause the robot to try to decelerate faster, at the risk of overshoots
|
||||
// or localization slippage.
|
||||
// Decreasing this will cause the deceleration at the end of the Path to be slower, making the
|
||||
// robot slower but reducing risk of end-of-path overshoots or localization slippage.
|
||||
// This can be set individually for each Path, but this is the default.
|
||||
public static double zeroPowerAccelerationMultiplier = ZERO_POWER_ACCEL_MULT;
|
||||
|
||||
|
||||
// When the robot is at the end of its current Path or PathChain and the velocity goes below
|
||||
// this value, then end the Path. This is in inches/second.
|
||||
// This can be custom set for each Path.
|
||||
public static double pathEndVelocityConstraint = 0.1;
|
||||
|
||||
// When the robot is at the end of its current Path or PathChain and the translational error
|
||||
// goes below this value, then end the Path. This is in inches.
|
||||
// This can be custom set for each Path.
|
||||
public static double pathEndTranslationalConstraint = 0.1;
|
||||
|
||||
// When the robot is at the end of its current Path or PathChain and the heading error goes
|
||||
// below this value, then end the Path. This is in radians.
|
||||
// This can be custom set for each Path.
|
||||
public static double pathEndHeadingConstraint = 0.007;
|
||||
|
||||
// When the t-value of the closest point to the robot on the Path is greater than this value,
|
||||
// then the Path is considered at its end.
|
||||
// This can be custom set for each Path.
|
||||
public static double pathEndTValueConstraint = 0.995;
|
||||
|
||||
// When the Path is considered at its end parametrically, then the Follower has this many
|
||||
// milliseconds to further correct by default.
|
||||
// This can be custom set for each Path.
|
||||
public static double pathEndTimeoutConstraint = 500;
|
||||
|
||||
// This is how many steps the BezierCurve class uses to approximate the length of a BezierCurve.
|
||||
public static int APPROXIMATION_STEPS = 1000;
|
||||
|
||||
// This is scales the translational error correction power when the Follower is holding a Point.
|
||||
public static double holdPointTranslationalScaling = 0.45;
|
||||
|
||||
// This is scales the heading error correction power when the Follower is holding a Point.
|
||||
public static double holdPointHeadingScaling = 0.35;
|
||||
|
||||
// This is the number of times the velocity is recorded for averaging when approximating a first
|
||||
// and second derivative for on the fly centripetal correction. The velocity is calculated using
|
||||
// half of this number of samples, and the acceleration uses all of this number of samples.
|
||||
public static int AVERAGED_VELOCITY_SAMPLE_NUMBER = 8;
|
||||
|
||||
// This is the number of steps the binary search for closest point uses. More steps is more
|
||||
// accuracy, and this increases at an exponential rate. However, more steps also does take more
|
||||
// time.
|
||||
public static int BEZIER_CURVE_BINARY_STEP_LIMIT = 10;
|
||||
|
||||
|
||||
// These activate / deactivate the secondary PIDs. These take over at errors under a set limit for
|
||||
// the translational, heading, and drive PIDs.
|
||||
public static boolean useSecondaryTranslationalPID = false;
|
||||
public static boolean useSecondaryHeadingPID = false;
|
||||
public static boolean useSecondaryDrivePID = false;
|
||||
|
||||
|
||||
// the limit at which the translational PIDF switches between the main and secondary translational PIDFs,
|
||||
// if the secondary PID is active
|
||||
public static double translationalPIDFSwitch = 3;
|
||||
|
||||
// Secondary translational PIDF coefficients (don't use integral)
|
||||
public static CustomPIDFCoefficients secondaryTranslationalPIDFCoefficients = new CustomPIDFCoefficients(
|
||||
0.3,
|
||||
0,
|
||||
0.01,
|
||||
0);
|
||||
|
||||
// Secondary translational Integral value
|
||||
public static CustomPIDFCoefficients secondaryTranslationalIntegral = new CustomPIDFCoefficients(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Feed forward constant added on to the small translational PIDF
|
||||
public static double secondaryTranslationalPIDFFeedForward = 0.015;
|
||||
|
||||
|
||||
// the limit at which the heading PIDF switches between the main and secondary heading PIDFs
|
||||
public static double headingPIDFSwitch = Math.PI/20;
|
||||
|
||||
// Secondary heading error PIDF coefficients
|
||||
public static CustomPIDFCoefficients secondaryHeadingPIDFCoefficients = new CustomPIDFCoefficients(
|
||||
5,
|
||||
0,
|
||||
0.08,
|
||||
0);
|
||||
|
||||
// Feed forward constant added on to the secondary heading PIDF
|
||||
public static double secondaryHeadingPIDFFeedForward = 0.01;
|
||||
|
||||
|
||||
// the limit at which the heading PIDF switches between the main and secondary drive PIDFs
|
||||
public static double drivePIDFSwitch = 20;
|
||||
|
||||
// Secondary drive PIDF coefficients
|
||||
public static CustomFilteredPIDFCoefficients secondaryDrivePIDFCoefficients = new CustomFilteredPIDFCoefficients(
|
||||
0.02,
|
||||
0,
|
||||
0.000005,
|
||||
0.6,
|
||||
0);
|
||||
|
||||
// Feed forward constant added on to the secondary drive PIDF
|
||||
public static double secondaryDrivePIDFFeedForward = 0.01;
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the ForwardVelocityTuner autonomous tuning OpMode. This runs the robot forwards at max
|
||||
* power until it reaches some specified distance. It records the most recent velocities, and on
|
||||
* reaching the end of the distance, it averages them and prints out the velocity obtained. It is
|
||||
* recommended to run this multiple times on a full battery to get the best results. What this does
|
||||
* is, when paired with StrafeVelocityTuner, allows FollowerConstants to create a Vector that
|
||||
* empirically represents the direction your mecanum wheels actually prefer to go in, allowing for
|
||||
* more accurate following.
|
||||
* You can adjust the distance the robot will travel on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/13/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Forward Velocity Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class ForwardVelocityTuner extends OpMode {
|
||||
private ArrayList<Double> velocities = new ArrayList<>();
|
||||
|
||||
private DcMotorEx leftFront;
|
||||
private DcMotorEx leftRear;
|
||||
private DcMotorEx rightFront;
|
||||
private DcMotorEx rightRear;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
private PoseUpdater poseUpdater;
|
||||
|
||||
public static double DISTANCE = 40;
|
||||
public static double RECORD_NUMBER = 10;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
private boolean end;
|
||||
|
||||
/**
|
||||
* This initializes the drive motors as well as the cache of velocities and the FTC Dashboard
|
||||
* telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, leftFrontMotorName);
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, leftRearMotorName);
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, rightRearMotorName);
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, rightFrontMotorName);
|
||||
|
||||
// TODO: Make sure that this is the direction your motors need to be reversed in.
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightFront, rightRear);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT);
|
||||
}
|
||||
|
||||
for (int i = 0; i < RECORD_NUMBER; i++) {
|
||||
velocities.add(0.0);
|
||||
}
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("The robot will run at 1 power until it reaches " + DISTANCE + " inches forward.");
|
||||
telemetryA.addLine("Make sure you have enough room, since the robot has inertia after cutting power.");
|
||||
telemetryA.addLine("After running the distance, the robot will cut power from the drivetrain and display the forward velocity.");
|
||||
telemetryA.addLine("Press CROSS or A on game pad 1 to stop.");
|
||||
telemetryA.update();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This starts the OpMode by setting the drive motors to run forward at full power.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
leftFront.setPower(1);
|
||||
leftRear.setPower(1);
|
||||
rightFront.setPower(1);
|
||||
rightRear.setPower(1);
|
||||
end = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode. At any point during the running of the OpMode, pressing CROSS or A on
|
||||
* game pad 1 will stop the OpMode. This continuously records the RECORD_NUMBER most recent
|
||||
* velocities, and when the robot has run forward enough, these last velocities recorded are
|
||||
* averaged and printed.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
if (gamepad1.cross || gamepad1.a) {
|
||||
requestOpModeStop();
|
||||
}
|
||||
|
||||
poseUpdater.update();
|
||||
if (!end) {
|
||||
if (Math.abs(poseUpdater.getPose().getX()) > DISTANCE) {
|
||||
end = true;
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
motor.setPower(0);
|
||||
}
|
||||
} else {
|
||||
double currentVelocity = Math.abs(MathFunctions.dotProduct(poseUpdater.getVelocity(), new Vector(1, 0)));
|
||||
velocities.add(currentVelocity);
|
||||
velocities.remove(0);
|
||||
}
|
||||
} else {
|
||||
double average = 0;
|
||||
for (Double velocity : velocities) {
|
||||
average += velocity;
|
||||
}
|
||||
average /= (double) velocities.size();
|
||||
|
||||
telemetryA.addData("forward velocity:", average);
|
||||
telemetryA.update();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the ForwardZeroPowerAccelerationTuner autonomous tuning OpMode. This runs the robot
|
||||
* forward until a specified velocity is achieved. Then, the robot cuts power to the motors, setting
|
||||
* them to zero power. The deceleration, or negative acceleration, is then measured until the robot
|
||||
* stops. The accelerations across the entire time the robot is slowing down is then averaged and
|
||||
* that number is then printed. This is used to determine how the robot will decelerate in the
|
||||
* forward direction when power is cut, making the estimations used in the calculations for the
|
||||
* drive Vector more accurate and giving better braking at the end of Paths.
|
||||
* You can adjust the max velocity the robot will hit on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/13/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Forward Zero Power Acceleration Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class ForwardZeroPowerAccelerationTuner extends OpMode {
|
||||
private ArrayList<Double> accelerations = new ArrayList<>();
|
||||
|
||||
private DcMotorEx leftFront;
|
||||
private DcMotorEx leftRear;
|
||||
private DcMotorEx rightFront;
|
||||
private DcMotorEx rightRear;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
private PoseUpdater poseUpdater;
|
||||
|
||||
public static double VELOCITY = 30;
|
||||
|
||||
private double previousVelocity;
|
||||
|
||||
private long previousTimeNano;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
private boolean stopping;
|
||||
private boolean end;
|
||||
|
||||
/**
|
||||
* This initializes the drive motors as well as the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, leftFrontMotorName);
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, leftRearMotorName);
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, rightRearMotorName);
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, rightFrontMotorName);
|
||||
|
||||
// TODO: Make sure that this is the direction your motors need to be reversed in.
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightFront, rightRear);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT);
|
||||
}
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("The robot will run forward until it reaches " + VELOCITY + " inches per second.");
|
||||
telemetryA.addLine("Then, it will cut power from the drivetrain and roll to a stop.");
|
||||
telemetryA.addLine("Make sure you have enough room.");
|
||||
telemetryA.addLine("After stopping, the forward zero power acceleration (natural deceleration) will be displayed.");
|
||||
telemetryA.addLine("Press CROSS or A on game pad 1 to stop.");
|
||||
telemetryA.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This starts the OpMode by setting the drive motors to run forward at full power.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
leftFront.setPower(1);
|
||||
leftRear.setPower(1);
|
||||
rightFront.setPower(1);
|
||||
rightRear.setPower(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode. At any point during the running of the OpMode, pressing CROSS or A on
|
||||
* game pad 1 will stop the OpMode. When the robot hits the specified velocity, the robot will
|
||||
* record its deceleration / negative acceleration until it stops. Then, it will average all the
|
||||
* recorded deceleration / negative acceleration and print that value.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
if (gamepad1.cross || gamepad1.a) {
|
||||
requestOpModeStop();
|
||||
}
|
||||
|
||||
poseUpdater.update();
|
||||
Vector heading = new Vector(1.0, poseUpdater.getPose().getHeading());
|
||||
if (!end) {
|
||||
if (!stopping) {
|
||||
if (MathFunctions.dotProduct(poseUpdater.getVelocity(), heading) > VELOCITY) {
|
||||
previousVelocity = MathFunctions.dotProduct(poseUpdater.getVelocity(), heading);
|
||||
previousTimeNano = System.nanoTime();
|
||||
stopping = true;
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setPower(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
double currentVelocity = MathFunctions.dotProduct(poseUpdater.getVelocity(), heading);
|
||||
accelerations.add((currentVelocity - previousVelocity) / ((System.nanoTime() - previousTimeNano) / Math.pow(10.0, 9)));
|
||||
previousVelocity = currentVelocity;
|
||||
previousTimeNano = System.nanoTime();
|
||||
if (currentVelocity < FollowerConstants.pathEndVelocityConstraint) {
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
double average = 0;
|
||||
for (Double acceleration : accelerations) {
|
||||
average += acceleration;
|
||||
}
|
||||
average /= (double)accelerations.size();
|
||||
|
||||
telemetryA.addData("forward zero power acceleration (deceleration):", average);
|
||||
telemetryA.update();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the LateralZeroPowerAccelerationTuner autonomous tuning OpMode. This runs the robot
|
||||
* to the right until a specified velocity is achieved. Then, the robot cuts power to the motors, setting
|
||||
* them to zero power. The deceleration, or negative acceleration, is then measured until the robot
|
||||
* stops. The accelerations across the entire time the robot is slowing down is then averaged and
|
||||
* that number is then printed. This is used to determine how the robot will decelerate in the
|
||||
* forward direction when power is cut, making the estimations used in the calculations for the
|
||||
* drive Vector more accurate and giving better braking at the end of Paths.
|
||||
* You can adjust the max velocity the robot will hit on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/13/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Lateral Zero Power Acceleration Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class LateralZeroPowerAccelerationTuner extends OpMode {
|
||||
private ArrayList<Double> accelerations = new ArrayList<>();
|
||||
|
||||
private DcMotorEx leftFront;
|
||||
private DcMotorEx leftRear;
|
||||
private DcMotorEx rightFront;
|
||||
private DcMotorEx rightRear;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
private PoseUpdater poseUpdater;
|
||||
|
||||
public static double VELOCITY = 50;
|
||||
|
||||
private double previousVelocity;
|
||||
|
||||
private long previousTimeNano;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
private boolean stopping;
|
||||
private boolean end;
|
||||
|
||||
/**
|
||||
* This initializes the drive motors as well as the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, leftFrontMotorName);
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, leftRearMotorName);
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, rightRearMotorName);
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, rightFrontMotorName);
|
||||
|
||||
// TODO: Make sure that this is the direction your motors need to be reversed in.
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightFront, rightRear);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT);
|
||||
}
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("The robot will run to the right until it reaches " + VELOCITY + " inches per second.");
|
||||
telemetryA.addLine("Then, it will cut power from the drivetrain and roll to a stop.");
|
||||
telemetryA.addLine("Make sure you have enough room.");
|
||||
telemetryA.addLine("After stopping, the lateral zero power acceleration (natural deceleration) will be displayed.");
|
||||
telemetryA.addLine("Press CROSS or A on game pad 1 to stop.");
|
||||
telemetryA.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This starts the OpMode by setting the drive motors to run forward at full power.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
leftFront.setPower(1);
|
||||
leftRear.setPower(-1);
|
||||
rightFront.setPower(-1);
|
||||
rightRear.setPower(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode. At any point during the running of the OpMode, pressing CROSS or A on
|
||||
* game pad 1 will stop the OpMode. When the robot hits the specified velocity, the robot will
|
||||
* record its deceleration / negative acceleration until it stops. Then, it will average all the
|
||||
* recorded deceleration / negative acceleration and print that value.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
if (gamepad1.cross || gamepad1.a) {
|
||||
requestOpModeStop();
|
||||
}
|
||||
|
||||
poseUpdater.update();
|
||||
Vector heading = new Vector(1.0, poseUpdater.getPose().getHeading() - Math.PI/2);
|
||||
if (!end) {
|
||||
if (!stopping) {
|
||||
if (MathFunctions.dotProduct(poseUpdater.getVelocity(), heading) > VELOCITY) {
|
||||
previousVelocity = MathFunctions.dotProduct(poseUpdater.getVelocity(), heading);
|
||||
previousTimeNano = System.nanoTime();
|
||||
stopping = true;
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setPower(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
double currentVelocity = MathFunctions.dotProduct(poseUpdater.getVelocity(), heading);
|
||||
accelerations.add((currentVelocity - previousVelocity) / ((System.nanoTime() - previousTimeNano) / Math.pow(10.0, 9)));
|
||||
previousVelocity = currentVelocity;
|
||||
previousTimeNano = System.nanoTime();
|
||||
if (currentVelocity < FollowerConstants.pathEndVelocityConstraint) {
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
double average = 0;
|
||||
for (Double acceleration : accelerations) {
|
||||
average += acceleration;
|
||||
}
|
||||
average /= (double)accelerations.size();
|
||||
|
||||
telemetryA.addData("lateral zero power acceleration (deceleration):", average);
|
||||
telemetryA.update();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.leftRearMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightFrontMotorName;
|
||||
import static org.firstinspires.ftc.teamcode.pedroPathing.tuning.FollowerConstants.rightRearMotorName;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
import com.qualcomm.robotcore.hardware.DcMotor;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorEx;
|
||||
import com.qualcomm.robotcore.hardware.DcMotorSimple;
|
||||
import com.qualcomm.robotcore.hardware.configuration.typecontainers.MotorConfigurationType;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the StrafeVelocityTuner autonomous tuning OpMode. This runs the robot right at max
|
||||
* power until it reaches some specified distance. It records the most recent velocities, and on
|
||||
* reaching the end of the distance, it averages them and prints out the velocity obtained. It is
|
||||
* recommended to run this multiple times on a full battery to get the best results. What this does
|
||||
* is, when paired with ForwardVelocityTuner, allows FollowerConstants to create a Vector that
|
||||
* empirically represents the direction your mecanum wheels actually prefer to go in, allowing for
|
||||
* more accurate following.
|
||||
* You can adjust the distance the robot will travel on FTC Dashboard: 192/168/43/1:8080/dash
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/13/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Strafe Velocity Tuner", group = "Autonomous Pathing Tuning")
|
||||
public class StrafeVelocityTuner extends OpMode {
|
||||
private ArrayList<Double> velocities = new ArrayList<>();
|
||||
|
||||
private DcMotorEx leftFront;
|
||||
private DcMotorEx leftRear;
|
||||
private DcMotorEx rightFront;
|
||||
private DcMotorEx rightRear;
|
||||
private List<DcMotorEx> motors;
|
||||
|
||||
private PoseUpdater poseUpdater;
|
||||
|
||||
public static double DISTANCE = 40;
|
||||
public static double RECORD_NUMBER = 10;
|
||||
|
||||
private Telemetry telemetryA;
|
||||
|
||||
private boolean end;
|
||||
|
||||
/**
|
||||
* This initializes the drive motors as well as the cache of velocities and the FTC Dashboard
|
||||
* telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
poseUpdater = new PoseUpdater(hardwareMap);
|
||||
|
||||
leftFront = hardwareMap.get(DcMotorEx.class, leftFrontMotorName);
|
||||
leftRear = hardwareMap.get(DcMotorEx.class, leftRearMotorName);
|
||||
rightRear = hardwareMap.get(DcMotorEx.class, rightRearMotorName);
|
||||
rightFront = hardwareMap.get(DcMotorEx.class, rightFrontMotorName);
|
||||
|
||||
// TODO: Make sure that this is the direction your motors need to be reversed in.
|
||||
leftFront.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
leftRear.setDirection(DcMotorSimple.Direction.REVERSE);
|
||||
|
||||
motors = Arrays.asList(leftFront, leftRear, rightFront, rightRear);
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
MotorConfigurationType motorConfigurationType = motor.getMotorType().clone();
|
||||
motorConfigurationType.setAchieveableMaxRPMFraction(1.0);
|
||||
motor.setMotorType(motorConfigurationType);
|
||||
}
|
||||
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT);
|
||||
}
|
||||
|
||||
for (int i = 0; i < RECORD_NUMBER; i++) {
|
||||
velocities.add(0.0);
|
||||
}
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("The robot will run at 1 power until it reaches " + DISTANCE + " inches to the right.");
|
||||
telemetryA.addLine("Make sure you have enough room, since the robot has inertia after cutting power.");
|
||||
telemetryA.addLine("After running the distance, the robot will cut power from the drivetrain and display the strafe velocity.");
|
||||
telemetryA.addLine("Press CROSS or A on game pad 1 to stop.");
|
||||
telemetryA.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This starts the OpMode by setting the drive motors to run right at full power.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
leftFront.setPower(1);
|
||||
leftRear.setPower(-1);
|
||||
rightFront.setPower(-1);
|
||||
rightRear.setPower(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode. At any point during the running of the OpMode, pressing CROSS or A on
|
||||
* game pad1 will stop the OpMode. This continuously records the RECORD_NUMBER most recent
|
||||
* velocities, and when the robot has run sideways enough, these last velocities recorded are
|
||||
* averaged and printed.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
if (gamepad1.cross || gamepad1.a) {
|
||||
requestOpModeStop();
|
||||
}
|
||||
|
||||
poseUpdater.update();
|
||||
if (!end) {
|
||||
if (Math.abs(poseUpdater.getPose().getY()) > DISTANCE) {
|
||||
end = true;
|
||||
for (DcMotorEx motor : motors) {
|
||||
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
|
||||
motor.setPower(0);
|
||||
}
|
||||
} else {
|
||||
double currentVelocity = Math.abs(MathFunctions.dotProduct(poseUpdater.getVelocity(), new Vector(1, Math.PI/2)));
|
||||
velocities.add(currentVelocity);
|
||||
velocities.remove(0);
|
||||
}
|
||||
} else {
|
||||
double average = 0;
|
||||
for (Double velocity : velocities) {
|
||||
average += velocity;
|
||||
}
|
||||
average /= (double) velocities.size();
|
||||
|
||||
telemetryA.addData("strafe velocity:", average);
|
||||
telemetryA.update();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.tuning;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
import com.acmerobotics.dashboard.telemetry.MultipleTelemetry;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
|
||||
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.follower.Follower;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.BezierLine;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Path;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Point;
|
||||
|
||||
/**
|
||||
* This is the StraightBackAndForth autonomous OpMode. It runs the robot in a specified distance
|
||||
* straight forward. On reaching the end of the forward Path, the robot runs the backward Path the
|
||||
* same distance back to the start. Rinse and repeat! This is good for testing a variety of Vectors,
|
||||
* like the drive Vector, the translational Vector, and the heading Vector. Remember to test your
|
||||
* tunings on CurvedBackAndForth as well, since tunings that work well for straight lines might
|
||||
* have issues going in curves.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/12/2024
|
||||
*/
|
||||
@Config
|
||||
@Autonomous (name = "Straight Back And Forth", group = "Autonomous Pathing Tuning")
|
||||
public class StraightBackAndForth extends OpMode {
|
||||
private Telemetry telemetryA;
|
||||
|
||||
public static double DISTANCE = 40;
|
||||
|
||||
private boolean forward = true;
|
||||
|
||||
private Follower follower;
|
||||
|
||||
private Path forwards;
|
||||
private Path backwards;
|
||||
|
||||
/**
|
||||
* This initializes the Follower and creates the forward and backward Paths. Additionally, this
|
||||
* initializes the FTC Dashboard telemetry.
|
||||
*/
|
||||
@Override
|
||||
public void init() {
|
||||
follower = new Follower(hardwareMap);
|
||||
|
||||
forwards = new Path(new BezierLine(new Point(0,0, Point.CARTESIAN), new Point(DISTANCE,0, Point.CARTESIAN)));
|
||||
forwards.setConstantHeadingInterpolation(0);
|
||||
backwards = new Path(new BezierLine(new Point(DISTANCE,0, Point.CARTESIAN), new Point(0,0, Point.CARTESIAN)));
|
||||
backwards.setConstantHeadingInterpolation(0);
|
||||
|
||||
follower.followPath(forwards);
|
||||
|
||||
telemetryA = new MultipleTelemetry(this.telemetry, FtcDashboard.getInstance().getTelemetry());
|
||||
telemetryA.addLine("This will run the robot in a straight line going " + DISTANCE
|
||||
+ " inches forward. The robot will go forward and backward continuously"
|
||||
+ " along the path. Make sure you have enough room.");
|
||||
telemetryA.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* This runs the OpMode, updating the Follower as well as printing out the debug statements to
|
||||
* the Telemetry, as well as the FTC Dashboard.
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
follower.update();
|
||||
if (!follower.isBusy()) {
|
||||
if (forward) {
|
||||
forward = false;
|
||||
follower.followPath(backwards);
|
||||
} else {
|
||||
forward = true;
|
||||
follower.followPath(forwards);
|
||||
}
|
||||
}
|
||||
|
||||
telemetryA.addData("going forward", forward);
|
||||
follower.telemetryDebug(telemetryA);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
import kotlin.jvm.JvmField;
|
||||
|
||||
/**
|
||||
* This is the CustomFilteredPIDFCoefficients class. This class handles holding coefficients for filtered PIDF
|
||||
* controllers.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 7/15/2024
|
||||
*/
|
||||
public class CustomFilteredPIDFCoefficients {
|
||||
@JvmField public double P;
|
||||
@JvmField public double I;
|
||||
@JvmField public double D;
|
||||
@JvmField public double T;
|
||||
@JvmField public double F;
|
||||
|
||||
public FeedForwardConstant feedForwardConstantEquation;
|
||||
|
||||
private boolean usingEquation;
|
||||
|
||||
/**
|
||||
* This creates a new CustomFilteredPIDFCoefficients with constant coefficients.
|
||||
*
|
||||
* @param p the coefficient for the proportional factor.
|
||||
* @param i the coefficient for the integral factor.
|
||||
* @param d the coefficient for the derivative factor.
|
||||
* @param t the time constant for the filter
|
||||
* @param f the coefficient for the feedforward factor.
|
||||
*/
|
||||
public CustomFilteredPIDFCoefficients(double p, double i, double d, double t, double f) {
|
||||
P = p;
|
||||
I = i;
|
||||
D = d;
|
||||
T = t;
|
||||
F = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new CustomFilteredPIDFCoefficients with constant PID coefficients and a variable
|
||||
* feedforward equation using a FeedForwardConstant.
|
||||
*
|
||||
* @param p the coefficient for the proportional factor.
|
||||
* @param i the coefficient for the integral factor.
|
||||
* @param d the coefficient for the derivative factor.
|
||||
* @param t the time constant for the filter
|
||||
* @param f the equation for the feedforward factor.
|
||||
*/
|
||||
public CustomFilteredPIDFCoefficients(double p, double i, double d, double t, FeedForwardConstant f) {
|
||||
usingEquation = true;
|
||||
P = p;
|
||||
I = i;
|
||||
D = d;
|
||||
T = t;
|
||||
feedForwardConstantEquation = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the coefficient for the feedforward factor.
|
||||
*
|
||||
* @param input this is inputted into the feedforward equation, if applicable. If there's no
|
||||
* equation, then any input can be used.
|
||||
* @return This returns the coefficient for the feedforward factor.
|
||||
*/
|
||||
public double getCoefficient(double input) {
|
||||
if (!usingEquation) return F;
|
||||
return feedForwardConstantEquation.getConstant(input);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
import kotlin.jvm.JvmField;
|
||||
|
||||
/**
|
||||
* This is the CustomPIDFCoefficients class. This class handles holding coefficients for PIDF
|
||||
* controllers.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/5/2024
|
||||
*/
|
||||
public class CustomPIDFCoefficients {
|
||||
@JvmField public double P;
|
||||
@JvmField public double I;
|
||||
@JvmField public double D;
|
||||
@JvmField public double F;
|
||||
|
||||
public FeedForwardConstant feedForwardConstantEquation;
|
||||
|
||||
private boolean usingEquation;
|
||||
|
||||
/**
|
||||
* This creates a new CustomPIDFCoefficients with constant coefficients.
|
||||
*
|
||||
* @param p the coefficient for the proportional factor.
|
||||
* @param i the coefficient for the integral factor.
|
||||
* @param d the coefficient for the derivative factor.
|
||||
* @param f the coefficient for the feedforward factor.
|
||||
*/
|
||||
public CustomPIDFCoefficients(double p, double i, double d, double f) {
|
||||
P = p;
|
||||
I = i;
|
||||
D = d;
|
||||
F = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* This creates a new CustomPIDFCoefficients with constant PID coefficients and a variable
|
||||
* feedforward equation using a FeedForwardConstant.
|
||||
*
|
||||
* @param p the coefficient for the proportional factor.
|
||||
* @param i the coefficient for the integral factor.
|
||||
* @param d the coefficient for the derivative factor.
|
||||
* @param f the equation for the feedforward factor.
|
||||
*/
|
||||
public CustomPIDFCoefficients(double p, double i, double d, FeedForwardConstant f) {
|
||||
usingEquation = true;
|
||||
P = p;
|
||||
I = i;
|
||||
D = d;
|
||||
feedForwardConstantEquation = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the coefficient for the feedforward factor.
|
||||
*
|
||||
* @param input this is inputted into the feedforward equation, if applicable. If there's no
|
||||
* equation, then any input can be used.
|
||||
* @return This returns the coefficient for the feedforward factor.
|
||||
*/
|
||||
public double getCoefficient(double input) {
|
||||
if (!usingEquation) return F;
|
||||
return feedForwardConstantEquation.getConstant(input);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.follower.Follower;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.PoseUpdater;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is the DashboardPoseTracker class. This tracks the pose history of the robot through a
|
||||
* PoseUpdater, adding to the pose history at specified increments of time and storing the history
|
||||
* for a specified length of time.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 5/13/2024
|
||||
*/
|
||||
public class DashboardPoseTracker {
|
||||
private double[] xPositions;
|
||||
private double[] yPositions;
|
||||
private PoseUpdater poseUpdater;
|
||||
private long lastUpdateTime;
|
||||
private final int TRACKING_LENGTH = 1500;
|
||||
private final long UPDATE_TIME = 50;
|
||||
private final int TRACKING_SIZE = TRACKING_LENGTH / (int) UPDATE_TIME;
|
||||
|
||||
/**
|
||||
* This creates a new DashboardPoseTracker from a PoseUpdater.
|
||||
*
|
||||
* @param poseUpdater the PoseUpdater
|
||||
*/
|
||||
public DashboardPoseTracker(PoseUpdater poseUpdater) {
|
||||
this.poseUpdater = poseUpdater;
|
||||
xPositions = new double[TRACKING_SIZE];
|
||||
yPositions = new double[TRACKING_SIZE];
|
||||
|
||||
for (int i = 0; i < TRACKING_SIZE; i++) {
|
||||
xPositions[i] = poseUpdater.getPose().getX();
|
||||
yPositions[i] = poseUpdater.getPose().getY();
|
||||
}
|
||||
|
||||
lastUpdateTime = System.currentTimeMillis() - UPDATE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the DashboardPoseTracker. When the specified update time has passed from the last
|
||||
* pose history log, another pose can be logged. The least recent log is also removed.
|
||||
*/
|
||||
public void update() {
|
||||
if (System.currentTimeMillis() - lastUpdateTime > UPDATE_TIME) {
|
||||
lastUpdateTime = System.currentTimeMillis();
|
||||
for (int i = TRACKING_SIZE - 1; i > 0; i--) {
|
||||
xPositions[i] = xPositions[i - 1];
|
||||
yPositions[i] = yPositions[i - 1];
|
||||
}
|
||||
xPositions[0] = poseUpdater.getPose().getX();
|
||||
yPositions[0] = poseUpdater.getPose().getY();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the x positions of the pose history as an Array of doubles.
|
||||
*
|
||||
* @return returns the x positions of the pose history
|
||||
*/
|
||||
public double[] getXPositionsArray() {
|
||||
return xPositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the y positions of the pose history as an Array of doubles.
|
||||
*
|
||||
* @return returns the y positions of the pose history
|
||||
*/
|
||||
public double[] getYPositionsArray() {
|
||||
return yPositions;
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
import com.acmerobotics.dashboard.FtcDashboard;
|
||||
import com.acmerobotics.dashboard.canvas.Canvas;
|
||||
import com.acmerobotics.dashboard.telemetry.TelemetryPacket;
|
||||
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.follower.Follower;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.localization.Pose;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.MathFunctions;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Path;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.PathChain;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Point;
|
||||
import org.firstinspires.ftc.teamcode.pedroPathing.pathGeneration.Vector;
|
||||
|
||||
/**
|
||||
* This is the Drawing class. It handles the drawing of stuff on FTC Dashboard, like the robot.
|
||||
*
|
||||
* @author Logan Nash
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 4/22/2024
|
||||
*/
|
||||
public class Drawing {
|
||||
private static TelemetryPacket packet;
|
||||
|
||||
/**
|
||||
* This draws everything that will be used in the Follower's telemetryDebug() method. This takes
|
||||
* a Follower as an input, so an instance of the DashbaordDrawingHandler class is not needed.
|
||||
* @param follower
|
||||
*/
|
||||
public static void drawDebug(Follower follower) {
|
||||
if (follower.getCurrentPath() != null) {
|
||||
drawPath(follower.getCurrentPath(), "#3F51B5");
|
||||
Point closestPoint = follower.getPointFromPath(follower.getCurrentPath().getClosestPointTValue());
|
||||
drawRobot(new Pose(closestPoint.getX(), closestPoint.getY(), follower.getCurrentPath().getHeadingGoal(follower.getCurrentPath().getClosestPointTValue())), "#3F51B5");
|
||||
}
|
||||
drawPoseHistory(follower.getDashboardPoseTracker(), "#4CAF50");
|
||||
drawRobot(follower.getPose(), "#4CAF50");
|
||||
sendPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds instructions to the current packet to draw a robot at a specified Pose with a specified
|
||||
* color. If no packet exists, then a new one is created.
|
||||
*
|
||||
* @param pose the Pose to draw the robot at
|
||||
* @param color the color to draw the robot with
|
||||
*/
|
||||
public static void drawRobot(Pose pose, String color) {
|
||||
if (packet == null) packet = new TelemetryPacket();
|
||||
|
||||
packet.fieldOverlay().setStroke(color);
|
||||
Drawing.drawRobotOnCanvas(packet.fieldOverlay(), pose.copy());
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds instructions to the current packet to draw a Path with a specified color. If no
|
||||
* packet exists, then a new one is created.
|
||||
*
|
||||
* @param path the Path to draw
|
||||
* @param color the color to draw the Path with
|
||||
*/
|
||||
public static void drawPath(Path path, String color) {
|
||||
if (packet == null) packet = new TelemetryPacket();
|
||||
|
||||
packet.fieldOverlay().setStroke(color);
|
||||
Drawing.drawPath(packet.fieldOverlay(), path.getDashboardDrawingPoints());
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds instructions to the current packet to draw all the Paths in a PathChain with a
|
||||
* specified color. If no packet exists, then a new one is created.
|
||||
*
|
||||
* @param pathChain the PathChain to draw
|
||||
* @param color the color to draw the PathChain with
|
||||
*/
|
||||
public static void drawPath(PathChain pathChain, String color) {
|
||||
for (int i = 0; i < pathChain.size(); i++) {
|
||||
drawPath(pathChain.getPath(i), color);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds instructions to the current packet to draw the pose history of the robot. If no
|
||||
* packet exists, then a new one is created.
|
||||
*
|
||||
* @param poseTracker the DashboardPoseTracker to get the pose history from
|
||||
* @param color the color to draw the pose history with
|
||||
*/
|
||||
public static void drawPoseHistory(DashboardPoseTracker poseTracker, String color) {
|
||||
if (packet == null) packet = new TelemetryPacket();
|
||||
|
||||
packet.fieldOverlay().setStroke(color);
|
||||
packet.fieldOverlay().strokePolyline(poseTracker.getXPositionsArray(), poseTracker.getYPositionsArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* This tries to send the current packet to FTC Dashboard.
|
||||
*
|
||||
* @return returns if the operation was successful.
|
||||
*/
|
||||
public static boolean sendPacket() {
|
||||
if (packet != null) {
|
||||
FtcDashboard.getInstance().sendTelemetryPacket(packet);
|
||||
packet = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This draws a robot on the Dashboard at a specified Point.
|
||||
*
|
||||
* @param c the Canvas on the Dashboard on which this will draw
|
||||
* @param t the Point to draw at
|
||||
*/
|
||||
public static void drawRobotOnCanvas(Canvas c, Point t) {
|
||||
final double ROBOT_RADIUS = 9;
|
||||
|
||||
c.setStrokeWidth(1);
|
||||
c.strokeCircle(t.getX(), t.getY(), ROBOT_RADIUS);
|
||||
|
||||
Vector halfv = new Vector(0.5*ROBOT_RADIUS, t.getTheta());
|
||||
Vector p1 = MathFunctions.addVectors(halfv, new Vector(t.getR(), t.getTheta()));
|
||||
Vector p2 = MathFunctions.addVectors(p1, halfv);
|
||||
c.strokeLine(p1.getXComponent(), p1.getYComponent(), p2.getXComponent(), p2.getYComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* This draws a robot on the Dashboard at a specified Pose. This is more useful for drawing the
|
||||
* actual robot, since the Pose contains the direction the robot is facing as well as its position.
|
||||
*
|
||||
* @param c the Canvas on the Dashboard on which this will draw
|
||||
* @param t the Pose to draw at
|
||||
*/
|
||||
public static void drawRobotOnCanvas(Canvas c, Pose t) {
|
||||
final double ROBOT_RADIUS = 9;
|
||||
|
||||
c.strokeCircle(t.getX(), t.getY(), ROBOT_RADIUS);
|
||||
Vector v = t.getHeadingVector();
|
||||
v.setMagnitude(v.getMagnitude() * ROBOT_RADIUS);
|
||||
double x1 = t.getX() + v.getXComponent() / 2, y1 = t.getY() + v.getYComponent() / 2;
|
||||
double x2 = t.getX() + v.getXComponent(), y2 = t.getY() + v.getYComponent();
|
||||
c.strokeLine(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* This draws a Path on the Dashboard from a specified Array of Points.
|
||||
*
|
||||
* @param c the Canvas on the Dashboard on which this will draw
|
||||
* @param points the Points to draw
|
||||
*/
|
||||
public static void drawPath(Canvas c, double[][] points) {
|
||||
c.strokePolyline(points[0], points[1]);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
/**
|
||||
* This is the FeedForwardConstant interface. This interface holds a feedforward equation for PIDFs.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/5/2024
|
||||
*/
|
||||
public interface FeedForwardConstant {
|
||||
|
||||
/**
|
||||
* This returns the coefficient for the feedforward factor.
|
||||
*
|
||||
* @param input this is inputted into the feedforward equation, if applicable. If there's no
|
||||
* equation, then any input can be used.
|
||||
* @return This returns the coefficient for the feedforward factor.
|
||||
*/
|
||||
double getConstant(double input);
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
/**
|
||||
* This is the FilteredPIDFController class. This class handles the running of filtered filtered PIDFs. This
|
||||
* behaves very similarly to a regular filtered PIDF controller, but the derivative portion is filtered with
|
||||
* a low pass filter to reduce high frequency noise that could affect results.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 7/15/2024
|
||||
*/
|
||||
public class FilteredPIDFController {
|
||||
private CustomFilteredPIDFCoefficients coefficients;
|
||||
|
||||
private double previousError;
|
||||
private double error;
|
||||
private double position;
|
||||
private double targetPosition;
|
||||
private double errorIntegral;
|
||||
private double errorDerivative;
|
||||
private double previousDerivative;
|
||||
private double filteredDerivative;
|
||||
private double feedForwardInput;
|
||||
|
||||
private long previousUpdateTimeNano;
|
||||
private long deltaTimeNano;
|
||||
|
||||
/**
|
||||
* This creates a new filtered PIDFController from a CustomPIDFCoefficients.
|
||||
*
|
||||
* @param set the coefficients to use.
|
||||
*/
|
||||
public FilteredPIDFController(CustomFilteredPIDFCoefficients set) {
|
||||
setCoefficients(set);
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes the current error and runs the filtered PIDF on it.
|
||||
*
|
||||
* @return this returns the value of the filtered PIDF from the current error.
|
||||
*/
|
||||
public double runPIDF() {
|
||||
return error * P() + filteredDerivative * D() + errorIntegral * I() + F();
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to update the filtered PIDF's current position when inputting a current position and
|
||||
* a target position to calculate error. This will update the error from the current position to
|
||||
* the target position specified.
|
||||
*
|
||||
* @param update This is the current position.
|
||||
*/
|
||||
public void updatePosition(double update) {
|
||||
position = update;
|
||||
previousError = error;
|
||||
error = targetPosition - position;
|
||||
|
||||
deltaTimeNano = System.nanoTime() - previousUpdateTimeNano;
|
||||
previousUpdateTimeNano = System.nanoTime();
|
||||
|
||||
errorIntegral += error * (deltaTimeNano / Math.pow(10.0, 9));
|
||||
previousDerivative = filteredDerivative;
|
||||
errorDerivative = (error - previousError) / (deltaTimeNano / Math.pow(10.0, 9));
|
||||
filteredDerivative = T() * previousDerivative + (1 - T()) * errorDerivative;
|
||||
}
|
||||
|
||||
/**
|
||||
* As opposed to updating position against a target position, this just sets the error to some
|
||||
* specified value.
|
||||
*
|
||||
* @param error The error specified.
|
||||
*/
|
||||
public void updateError(double error) {
|
||||
previousError = this.error;
|
||||
this.error = error;
|
||||
|
||||
deltaTimeNano = System.nanoTime() - previousUpdateTimeNano;
|
||||
previousUpdateTimeNano = System.nanoTime();
|
||||
|
||||
errorIntegral += error * (deltaTimeNano / Math.pow(10.0, 9));
|
||||
previousDerivative = errorDerivative;
|
||||
errorDerivative = (error - previousError) / (deltaTimeNano / Math.pow(10.0, 9));
|
||||
filteredDerivative = T() * previousDerivative + (1 - T()) * errorDerivative;
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to update the feedforward equation's input, if applicable.
|
||||
*
|
||||
* @param input the input into the feedforward equation.
|
||||
*/
|
||||
public void updateFeedForwardInput(double input) {
|
||||
feedForwardInput = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets all the filtered PIDF's error and position values, as well as the time stamps.
|
||||
*/
|
||||
public void reset() {
|
||||
previousError = 0;
|
||||
error = 0;
|
||||
position = 0;
|
||||
targetPosition = 0;
|
||||
errorIntegral = 0;
|
||||
errorDerivative = 0;
|
||||
previousDerivative = 0;
|
||||
filteredDerivative = 0;
|
||||
previousUpdateTimeNano = System.nanoTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to set the target position if the filtered PIDF is being run with current position and
|
||||
* target position inputs rather than error inputs.
|
||||
*
|
||||
* @param set this sets the target position.
|
||||
*/
|
||||
public void setTargetPosition(double set) {
|
||||
targetPosition = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the target position of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the target position.
|
||||
*/
|
||||
public double getTargetPosition() {
|
||||
return targetPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to set the coefficients of the filtered PIDF.
|
||||
*
|
||||
* @param set the coefficients that the filtered PIDF will use.
|
||||
*/
|
||||
public void setCoefficients(CustomFilteredPIDFCoefficients set) {
|
||||
coefficients = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the filtered PIDF's current coefficients.
|
||||
*
|
||||
* @return this returns the current coefficients.
|
||||
*/
|
||||
public CustomFilteredPIDFCoefficients getCoefficients() {
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the proportional (P) coefficient of the filtered PIDF only.
|
||||
*
|
||||
* @param set this sets the P coefficient.
|
||||
*/
|
||||
public void setP(double set) {
|
||||
coefficients.P = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the proportional (P) coefficient of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the P coefficient.
|
||||
*/
|
||||
public double P() {
|
||||
return coefficients.P;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the integral (I) coefficient of the filtered PIDF only.
|
||||
*
|
||||
* @param set this sets the I coefficient.
|
||||
*/
|
||||
public void setI(double set) {
|
||||
coefficients.I = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the integral (I) coefficient of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the I coefficient.
|
||||
*/
|
||||
public double I() {
|
||||
return coefficients.I;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the derivative (D) coefficient of the filtered PIDF only.
|
||||
*
|
||||
* @param set this sets the D coefficient.
|
||||
*/
|
||||
public void setD(double set) {
|
||||
coefficients.D = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the derivative (D) coefficient of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the D coefficient.
|
||||
*/
|
||||
public double D() {
|
||||
return coefficients.D;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the time constant (T) of the filtered PIDF only.
|
||||
*
|
||||
* @param set this sets the time constant.
|
||||
*/
|
||||
public void setT(double set) {
|
||||
coefficients.T = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the time constant (T) of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the time constant.
|
||||
*/
|
||||
public double T() {
|
||||
return coefficients.T;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the feedforward (F) constant of the filtered PIDF only.
|
||||
*
|
||||
* @param set this sets the F constant.
|
||||
*/
|
||||
public void setF(double set) {
|
||||
coefficients.F = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the feedforward (F) constant of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the F constant.
|
||||
*/
|
||||
public double F() {
|
||||
return coefficients.getCoefficient(feedForwardInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current error of the filtered PIDF.
|
||||
*
|
||||
* @return this returns the error.
|
||||
*/
|
||||
public double getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
import com.acmerobotics.dashboard.config.Config;
|
||||
|
||||
import org.firstinspires.ftc.robotcore.external.Telemetry;
|
||||
|
||||
/**
|
||||
* This is the KalmanFilter class. This creates a Kalman filter that is used to smooth out data.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 7/17/2024
|
||||
*/
|
||||
public class KalmanFilter {
|
||||
private KalmanFilterParameters parameters;
|
||||
private double state;
|
||||
private double variance;
|
||||
private double kalmanGain;
|
||||
private double previousState;
|
||||
private double previousVariance;
|
||||
|
||||
public KalmanFilter(KalmanFilterParameters parameters) {
|
||||
this.parameters = parameters;
|
||||
reset();
|
||||
}
|
||||
|
||||
public KalmanFilter(KalmanFilterParameters parameters, double startState, double startVariance, double startGain) {
|
||||
this.parameters = parameters;
|
||||
reset(startState, startVariance, startGain);
|
||||
}
|
||||
|
||||
public void reset(double startState, double startVariance, double startGain) {
|
||||
state = startState;
|
||||
previousState = startState;
|
||||
variance = startVariance;
|
||||
previousVariance = startVariance;
|
||||
kalmanGain = startGain;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
reset(0, 1, 1);
|
||||
}
|
||||
|
||||
public void update(double updateData, double updateProjection) {
|
||||
state = previousState + updateData;
|
||||
variance = previousVariance + parameters.modelCovariance;
|
||||
kalmanGain = variance / (variance + parameters.dataCovariance);
|
||||
state += kalmanGain * (updateProjection - state);
|
||||
variance *= (1.0 - kalmanGain);
|
||||
previousState = state;
|
||||
previousVariance = variance;
|
||||
}
|
||||
|
||||
public double getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void debug(Telemetry telemetry) {
|
||||
telemetry.addData("state", state);
|
||||
telemetry.addData("variance", variance);
|
||||
telemetry.addData("Kalman gain", kalmanGain);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
import kotlin.jvm.JvmField;
|
||||
|
||||
/**
|
||||
* This is the KalmanFilterParameters class. This class handles holding parameters Kalman filters.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @version 1.0, 7/17/2024
|
||||
*/
|
||||
public class KalmanFilterParameters {
|
||||
@JvmField public double modelCovariance;
|
||||
@JvmField public double dataCovariance;
|
||||
|
||||
/**
|
||||
* This creates a new KalmanFilterParameters with a specified model and data covariance.
|
||||
*
|
||||
* @param modelCovariance the covariance of the model.
|
||||
* @param dataCovariance the covariance of the data.
|
||||
*/
|
||||
public KalmanFilterParameters(double modelCovariance, double dataCovariance) {
|
||||
this.modelCovariance = modelCovariance;
|
||||
this.dataCovariance = dataCovariance;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
/**
|
||||
* This is the NanoTimer class. It is an elapsed time clock with nanosecond precision, or at least
|
||||
* as precise as the System.nanoTime() is.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/5/2024
|
||||
*/
|
||||
public class NanoTimer {
|
||||
private long startTime;
|
||||
|
||||
/**
|
||||
* This creates a new NanoTimer with the start time set to its creation time.
|
||||
*/
|
||||
public NanoTimer() {
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets the NanoTimer's start time to the current time using System.nanoTime().
|
||||
*/
|
||||
public void resetTimer() {
|
||||
startTime = System.nanoTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the elapsed time in nanoseconds since the start time of the NanoTimer.
|
||||
*
|
||||
* @return this returns the elapsed time in nanoseconds.
|
||||
*/
|
||||
public long getElapsedTime() {
|
||||
return System.nanoTime() - startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the elapsed time in seconds since the start time of the NanoTimer.
|
||||
*
|
||||
* @return this returns the elapsed time in seconds.
|
||||
*/
|
||||
public double getElapsedTimeSeconds() {
|
||||
return (getElapsedTime() / Math.pow(10.0,9));
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
package org.firstinspires.ftc.teamcode.pedroPathing.util;
|
||||
|
||||
/**
|
||||
* This is the PIDFController class. This class handles the running of PIDFs. PIDF stands for
|
||||
* proportional, integral, derivative, and feedforward. PIDFs take the error of a system as an input.
|
||||
* Coefficients multiply into the error, the integral of the error, the derivative of the error, and
|
||||
* a feedforward value. Then, these values are added up and returned. In this way, error in the
|
||||
* system is corrected.
|
||||
*
|
||||
* @author Anyi Lin - 10158 Scott's Bots
|
||||
* @author Aaron Yang - 10158 Scott's Bots
|
||||
* @author Harrison Womack - 10158 Scott's Bots
|
||||
* @version 1.0, 3/5/2024
|
||||
*/
|
||||
public class PIDFController {
|
||||
private CustomPIDFCoefficients coefficients;
|
||||
|
||||
private double previousError;
|
||||
private double error;
|
||||
private double position;
|
||||
private double targetPosition;
|
||||
private double errorIntegral;
|
||||
private double errorDerivative;
|
||||
private double feedForwardInput;
|
||||
|
||||
private long previousUpdateTimeNano;
|
||||
private long deltaTimeNano;
|
||||
|
||||
/**
|
||||
* This creates a new PIDFController from a CustomPIDFCoefficients.
|
||||
*
|
||||
* @param set the coefficients to use.
|
||||
*/
|
||||
public PIDFController(CustomPIDFCoefficients set) {
|
||||
setCoefficients(set);
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes the current error and runs the PIDF on it.
|
||||
*
|
||||
* @return this returns the value of the PIDF from the current error.
|
||||
*/
|
||||
public double runPIDF() {
|
||||
return error * P() + errorDerivative * D() + errorIntegral * I() + F();
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to update the PIDF's current position when inputting a current position and
|
||||
* a target position to calculate error. This will update the error from the current position to
|
||||
* the target position specified.
|
||||
*
|
||||
* @param update This is the current position.
|
||||
*/
|
||||
public void updatePosition(double update) {
|
||||
position = update;
|
||||
previousError = error;
|
||||
error = targetPosition - position;
|
||||
|
||||
deltaTimeNano = System.nanoTime() - previousUpdateTimeNano;
|
||||
previousUpdateTimeNano = System.nanoTime();
|
||||
|
||||
errorIntegral += error * (deltaTimeNano / Math.pow(10.0, 9));
|
||||
errorDerivative = (error - previousError) / (deltaTimeNano / Math.pow(10.0, 9));
|
||||
}
|
||||
|
||||
/**
|
||||
* As opposed to updating position against a target position, this just sets the error to some
|
||||
* specified value.
|
||||
*
|
||||
* @param error The error specified.
|
||||
*/
|
||||
public void updateError(double error) {
|
||||
previousError = this.error;
|
||||
this.error = error;
|
||||
|
||||
deltaTimeNano = System.nanoTime() - previousUpdateTimeNano;
|
||||
previousUpdateTimeNano = System.nanoTime();
|
||||
|
||||
errorIntegral += error * (deltaTimeNano / Math.pow(10.0, 9));
|
||||
errorDerivative = (error - previousError) / (deltaTimeNano / Math.pow(10.0, 9));
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to update the feedforward equation's input, if applicable.
|
||||
*
|
||||
* @param input the input into the feedforward equation.
|
||||
*/
|
||||
public void updateFeedForwardInput(double input) {
|
||||
feedForwardInput = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* This resets all the PIDF's error and position values, as well as the time stamps.
|
||||
*/
|
||||
public void reset() {
|
||||
previousError = 0;
|
||||
error = 0;
|
||||
position = 0;
|
||||
targetPosition = 0;
|
||||
errorIntegral = 0;
|
||||
errorDerivative = 0;
|
||||
previousUpdateTimeNano = System.nanoTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to set the target position if the PIDF is being run with current position and
|
||||
* target position inputs rather than error inputs.
|
||||
*
|
||||
* @param set this sets the target position.
|
||||
*/
|
||||
public void setTargetPosition(double set) {
|
||||
targetPosition = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the target position of the PIDF.
|
||||
*
|
||||
* @return this returns the target position.
|
||||
*/
|
||||
public double getTargetPosition() {
|
||||
return targetPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to set the coefficients of the PIDF.
|
||||
*
|
||||
* @param set the coefficients that the PIDF will use.
|
||||
*/
|
||||
public void setCoefficients(CustomPIDFCoefficients set) {
|
||||
coefficients = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the PIDF's current coefficients.
|
||||
*
|
||||
* @return this returns the current coefficients.
|
||||
*/
|
||||
public CustomPIDFCoefficients getCoefficients() {
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the proportional (P) coefficient of the PIDF only.
|
||||
*
|
||||
* @param set this sets the P coefficient.
|
||||
*/
|
||||
public void setP(double set) {
|
||||
coefficients.P = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the proportional (P) coefficient of the PIDF.
|
||||
*
|
||||
* @return this returns the P coefficient.
|
||||
*/
|
||||
public double P() {
|
||||
return coefficients.P;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the integral (I) coefficient of the PIDF only.
|
||||
*
|
||||
* @param set this sets the I coefficient.
|
||||
*/
|
||||
public void setI(double set) {
|
||||
coefficients.I = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the integral (I) coefficient of the PIDF.
|
||||
*
|
||||
* @return this returns the I coefficient.
|
||||
*/
|
||||
public double I() {
|
||||
return coefficients.I;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the derivative (D) coefficient of the PIDF only.
|
||||
*
|
||||
* @param set this sets the D coefficient.
|
||||
*/
|
||||
public void setD(double set) {
|
||||
coefficients.D = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the derivative (D) coefficient of the PIDF.
|
||||
*
|
||||
* @return this returns the D coefficient.
|
||||
*/
|
||||
public double D() {
|
||||
return coefficients.D;
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets the feedforward (F) constant of the PIDF only.
|
||||
*
|
||||
* @param set this sets the F constant.
|
||||
*/
|
||||
public void setF(double set) {
|
||||
coefficients.F = set;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the feedforward (F) constant of the PIDF.
|
||||
*
|
||||
* @return this returns the F constant.
|
||||
*/
|
||||
public double F() {
|
||||
return coefficients.getCoefficient(feedForwardInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the current error of the PIDF.
|
||||
*
|
||||
* @return this returns the error.
|
||||
*/
|
||||
public double getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user