diff --git a/build.gradle.kts b/build.gradle.kts index 372d314..1071494 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,19 +1,24 @@ plugins { java } - dependencies { // Testing (runs on PC without SDK) testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation("org.mockito:mockito-core:5.5.0") } - java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } +// Exclude FTC hardware files from PC compilation (they need Android SDK) +tasks.withType { + exclude("**/FtcMotor.java") + exclude("**/FtcPotentiometer.java") + exclude("**/opmodes/ChuteOpMode.java") +} + tasks.test { useJUnitPlatform() testLogging { @@ -22,7 +27,6 @@ tasks.test { exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL } } - // Task to deploy code to FTC SDK tasks.register("deployToSDK") { group = "ftc" @@ -40,7 +44,6 @@ tasks.register("deployToSDK") { println("✓ Code deployed to TeamCode") } } - // Task to build APK tasks.register("buildApk") { group = "ftc" @@ -60,4 +63,4 @@ tasks.register("buildApk") { doLast { println("✓ APK built successfully") } -} +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/opmodes/ChuteOpMode.java b/src/main/java/org/firstinspires/ftc/teamcode/opmodes/ChuteOpMode.java new file mode 100644 index 0000000..9da3c99 --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/opmodes/ChuteOpMode.java @@ -0,0 +1,122 @@ +package org.firstinspires.ftc.teamcode.opmodes; + +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; +import com.qualcomm.robotcore.hardware.DcMotor; +import com.qualcomm.robotcore.hardware.AnalogInput; + +import org.firstinspires.ftc.teamcode.subsystems.chute.Chute; +import org.firstinspires.ftc.teamcode.subsystems.chute.FtcMotor; +import org.firstinspires.ftc.teamcode.subsystems.chute.FtcPotentiometer; +import org.firstinspires.ftc.teamcode.subsystems.chute.ChuteController; + +/** + * Example OpMode demonstrating chute control with real hardware. + * + * Hardware Config: + * - Motor: "chute_motor" (DcMotor) + * - Potentiometer: "chute_pot" (AnalogInput) + */ +@TeleOp(name = "Chute Control") +public class ChuteOpMode extends LinearOpMode { + + private Chute chute; + + // Position presets (in radians) + private static final double HOME = 0.0; + private static final double LOW = Math.PI; + private static final double MID = 2 * Math.PI; + private static final double HIGH = 3 * Math.PI; + private static final double MAX = 4 * Math.PI; + + @Override + public void runOpMode() { + // Get hardware from config + DcMotor chuteMotor = hardwareMap.get(DcMotor.class, "chute_motor"); + AnalogInput chutePot = hardwareMap.get(AnalogInput.class, "chute_pot"); + + // Create hardware adapters + FtcMotor motor = new FtcMotor(chuteMotor); + FtcPotentiometer pot = new FtcPotentiometer(chutePot); + + // Create chute controller with real hardware + ChuteController controller = new ChuteController(motor, pot, MAX); + chute = new Chute(controller, motor, pot); + + telemetry.addLine("Chute initialized"); + telemetry.addLine("Press A to home"); + telemetry.update(); + + waitForStart(); + + while (opModeIsActive()) { + // Update chute (50Hz) + chute.update(0.02); + + // Control with gamepad + handleControls(); + + // Display status + updateTelemetry(); + + sleep(20); // 50Hz loop + } + } + + private void handleControls() { + // Manual homing + if (gamepad1.a) { + chute.home(); + } + + // Emergency stop + if (gamepad1.b) { + chute.stop(); + } + + // Position presets + if (gamepad1.dpad_down) { + chute.setTargetPosition(HOME); + } + if (gamepad1.dpad_left) { + chute.setTargetPosition(LOW); + } + if (gamepad1.dpad_right) { + chute.setTargetPosition(MID); + } + if (gamepad1.dpad_up) { + chute.setTargetPosition(HIGH); + } + + // Fine control with triggers + if (gamepad1.left_trigger > 0.1) { + double current = chute.getPosition(); + chute.setTargetPosition(current - 0.1); + } + if (gamepad1.right_trigger > 0.1) { + double current = chute.getPosition(); + chute.setTargetPosition(current + 0.1); + } + } + + private void updateTelemetry() { + telemetry.addLine("=== Chute Status ==="); + telemetry.addData("Homed", chute.isHomed() ? "YES" : "NO"); + telemetry.addData("Moving", chute.isMoving() ? "YES" : "NO"); + telemetry.addData("Position", "%.2f rad (%.0f°)", + chute.getPosition(), Math.toDegrees(chute.getPosition())); + + if (chute.isHomed()) { + double percent = (chute.getPosition() / MAX) * 100; + telemetry.addData("Extension", "%.0f%%", percent); + } + + telemetry.addLine(); + telemetry.addLine("=== Controls ==="); + telemetry.addLine("A: Home | B: Stop"); + telemetry.addLine("D-Pad: Presets"); + telemetry.addLine("Triggers: Fine adjust"); + + telemetry.update(); + } +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/Chute.java b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/Chute.java new file mode 100644 index 0000000..abb9376 --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/Chute.java @@ -0,0 +1,65 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +/** + * Simple chute subsystem. + */ +public class Chute { + private final ChuteController controller; + private final MockMotor motor; + private final MockPotentiometer pot; + + /** + * Create chute with mock hardware (for testing). + */ + public Chute(double maxExtension) { + this.motor = new MockMotor(); + this.pot = new MockPotentiometer(); + this.controller = new ChuteController(motor, pot, maxExtension); + } + + /** + * Create chute with real hardware (for robot). + * Use FtcMotor and FtcPotentiometer adapters. + */ + public Chute(ChuteController controller, MockMotor motor, MockPotentiometer pot) { + this.controller = controller; + this.motor = motor; + this.pot = pot; + } + + public void update(double dt) { + controller.update(dt); + } + + public void home() { + controller.home(); + } + + public void setTargetPosition(double position) { + controller.moveToPosition(position); + } + + public void stop() { + controller.stop(); + } + + public double getPosition() { + return controller.getPosition(); + } + + public boolean isHomed() { + return controller.isHomed(); + } + + public boolean isMoving() { + return controller.isMoving(); + } + + public boolean isAtTarget() { + return isHomed() && !isMoving(); + } + + // For testing + public MockMotor getMotor() { return motor; } + public MockPotentiometer getPot() { return pot; } +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/ChuteController.java b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/ChuteController.java new file mode 100644 index 0000000..d89271e --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/ChuteController.java @@ -0,0 +1,197 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +/** + * Simple chute controller with homing and position control. + */ +public class ChuteController { + private final MockMotor motor; + private final MockPotentiometer pot; + private final double maxExtension; + + private boolean homed = false; + private boolean homing = false; + private double homeVoltage = 0.0; + private double currentPosition = 0.0; + private int wrapCount = 0; + private double lastVoltage = 0.0; + private double targetPosition = 0.0; + private boolean moving = false; + private boolean needsToMove = false; + + private int stableCount = 0; + private static final int STABLE_REQUIRED = 5; + private static final double TOLERANCE = 0.15; // Generous tolerance + + /** + * Create a new chute controller. + * @param motor Motor that drives the chute + * @param pot Potentiometer that measures position + * @param maxExtension Maximum extension from home in radians + */ + public ChuteController(MockMotor motor, MockPotentiometer pot, double maxExtension) { + this.motor = motor; + this.pot = pot; + this.maxExtension = maxExtension; + } + + /** + * Start homing sequence - drives down until pot stops changing, then captures reference voltage. + */ + public void home() { + motor.setPower(-0.5); + homed = false; + homing = true; + stableCount = 0; + lastVoltage = pot.getVoltage(); + } + + /** + * Move to target position. Automatically homes first if not already homed. + * @param target Target position in radians from home + */ + public void moveToPosition(double target) { + target = Math.max(0, Math.min(maxExtension, target)); + targetPosition = target; + + if (!homed) { + needsToMove = true; + if (!homing) { + home(); + } + return; + } + + moving = true; + needsToMove = false; + + if (target > currentPosition) { + motor.setPower(0.7); + } else { + motor.setPower(-0.7); + } + } + + /** + * Update controller state - call this every loop iteration. + * @param dt Time since last update in seconds + */ + public void update(double dt) { + motor.update(dt); + pot.updatePosition(motor.getPower() * dt); + + double voltage = pot.getVoltage(); + + if (homing) { + updateHoming(voltage); + } else if (moving) { + updateMoving(voltage); + } + + lastVoltage = voltage; + } + + /** + * Update homing state - waits for stable pot readings then captures home voltage. + */ + private void updateHoming(double voltage) { + if (Math.abs(voltage - lastVoltage) < 0.01) { + stableCount++; + if (stableCount >= STABLE_REQUIRED) { + motor.setPower(0); + homeVoltage = voltage; + currentPosition = 0.0; + wrapCount = 0; + homed = true; + homing = false; + stableCount = 0; + + if (needsToMove) { + needsToMove = false; + moving = true; + if (targetPosition > 0) { + motor.setPower(0.7); + } + } + } + } else { + stableCount = 0; + } + } + + /** + * Update moving state - tracks wraps, calculates position, stops at target. + */ + private void updateMoving(double voltage) { + double delta = voltage - lastVoltage; + + // Detect wraps - look for large jumps + if (Math.abs(delta) > 2.0) { + if (motor.getPower() > 0) { + // Moving up: voltage wraps from ~0 to pi (positive jump) + wrapCount++; + } else { + // Moving down: voltage wraps from pi to ~0 (negative jump) + wrapCount--; + } + } + + // Calculate position: offset from home + wraps + double offset = voltage - homeVoltage; + if (offset < 0) offset += Math.PI; // Handle wraparound in offset + + currentPosition = offset + (wrapCount * Math.PI); + if (currentPosition < 0) currentPosition = 0; + + // Check if at target + if (Math.abs(currentPosition - targetPosition) < TOLERANCE) { + motor.setPower(0); + moving = false; + } + + // Safety limit + if (currentPosition >= maxExtension) { + motor.setPower(0); + moving = false; + } + } + + /** + * Check if chute has been homed. + * @return True if homing is complete + */ + public boolean isHomed() { + return homed; + } + + /** + * Check if chute is currently moving to target. + * @return True if moving + */ + public boolean isMoving() { + return moving; + } + + /** + * Get current position of chute. + * @return Position in radians from home + */ + public double getPosition() { + return currentPosition; + } + + /** + * Get the home voltage reference captured during homing. + * @return Home voltage in range [0, pi] + */ + public double getHomeVoltage() { + return homeVoltage; + } + + /** + * Emergency stop - immediately stops motor and cancels movement. + */ + public void stop() { + motor.setPower(0); + moving = false; + } +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/FtcMotor.java b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/FtcMotor.java new file mode 100644 index 0000000..090c4cd --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/FtcMotor.java @@ -0,0 +1,37 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +import com.qualcomm.robotcore.hardware.DcMotor; + +/** + * Adapter that wraps a real FTC DcMotor and makes it look like a MockMotor. + * Use this to connect the chute controller to actual hardware. + */ +public class FtcMotor extends MockMotor { + private final DcMotor motor; + + /** + * Create motor adapter. + * @param motor The real FTC motor from hardwareMap + */ + public FtcMotor(DcMotor motor) { + super(); + this.motor = motor; + + // Configure motor + motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); + motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER); + } + + @Override + public void setPower(double power) { + super.setPower(power); // Track power in parent + motor.setPower(power); // Send to real motor + } + + @Override + public void update(double dt) { + // Real motor updates itself - we don't simulate + // But we still call parent to track internal position for mock purposes + super.update(dt); + } +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/FtcPotentiometer.java b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/FtcPotentiometer.java new file mode 100644 index 0000000..a6e2e6c --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/FtcPotentiometer.java @@ -0,0 +1,44 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +import com.qualcomm.robotcore.hardware.AnalogInput; + +/** + * Adapter that wraps a real FTC AnalogInput (potentiometer) and makes it look like a MockPotentiometer. + * Use this to connect the chute controller to actual hardware. + */ +public class FtcPotentiometer extends MockPotentiometer { + private final AnalogInput pot; + private final double maxVoltage; + + /** + * Create potentiometer adapter. + * @param pot The real FTC analog input from hardwareMap + */ + public FtcPotentiometer(AnalogInput pot) { + super(); + this.pot = pot; + this.maxVoltage = pot.getMaxVoltage(); // Usually 3.3V + } + + @Override + public double getVoltage() { + // Read actual voltage from hardware + double rawVoltage = pot.getVoltage(); + + // Scale to [0, pi] range + // Assumes pot uses full voltage range (0 to maxVoltage) + return (rawVoltage / maxVoltage) * Math.PI; + } + + @Override + public void updatePosition(double delta) { + // Real pot updates itself - ignore this + // (This method is only used in simulation) + } + + @Override + public void setPosition(double pos) { + // Can't set real hardware position - ignore + // (This method is only used in simulation) + } +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/MockMotor.java b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/MockMotor.java new file mode 100644 index 0000000..7b4ad42 --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/MockMotor.java @@ -0,0 +1,36 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +/** + * Simple mock motor. + */ +public class MockMotor { + private double power; + private double position; + private double speed = 1.0; // rad/s at full power + + public MockMotor() { + this.power = 0.0; + this.position = 0.0; + } + + public void setPower(double power) { + this.power = Math.max(-1.0, Math.min(1.0, power)); + } + + public double getPower() { + return power; + } + + public void update(double dt) { + position += power * speed * dt; + if (position < 0) position = 0; // Hard stop at home + } + + public double getPosition() { + return position; + } + + public void setPosition(double pos) { + this.position = pos; + } +} \ No newline at end of file diff --git a/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/MockPotentiometer.java b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/MockPotentiometer.java new file mode 100644 index 0000000..63421be --- /dev/null +++ b/src/main/java/org/firstinspires/ftc/teamcode/subsystems/chute/MockPotentiometer.java @@ -0,0 +1,27 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +/** + * Simple mock potentiometer with wraparound at pi. + */ +public class MockPotentiometer { + private double position; + + public MockPotentiometer() { + this.position = 0.0; + } + + public double getVoltage() { + // Wrap to [0, pi] + double wrapped = position % Math.PI; + if (wrapped < 0) wrapped += Math.PI; + return wrapped; + } + + public void setPosition(double pos) { + this.position = pos; + } + + public void updatePosition(double delta) { + this.position += delta; + } +} \ No newline at end of file diff --git a/src/main/java/robot/hardware/.gitkeep b/src/main/java/robot/hardware/.gitkeep deleted file mode 100644 index 5a7eeea..0000000 --- a/src/main/java/robot/hardware/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# This file ensures the directory is tracked by git even when empty diff --git a/src/main/java/robot/opmodes/BasicOpMode.java b/src/main/java/robot/opmodes/BasicOpMode.java deleted file mode 100644 index acbafc6..0000000 --- a/src/main/java/robot/opmodes/BasicOpMode.java +++ /dev/null @@ -1,27 +0,0 @@ -package robot.opmodes; - -/** - * Basic OpMode for chute-drive - * - * This is a placeholder to demonstrate project structure. - * To use this with FTC SDK: - * 1. Run: weevil deploy chute-drive - * 2. Add FTC SDK imports (OpMode, TeleOp, etc.) - * 3. Extend OpMode and implement methods - * - * For local testing (without robot), write unit tests in src/test/java/robot/ - * Run tests with: ./gradlew test - * - * Created by Weevil 1.1.0-rc1 - * Template: basic - */ -public class BasicOpMode { - - // This placeholder compiles without FTC SDK dependencies - // Replace with actual OpMode code when deploying to robot - - public static void main(String[] args) { - System.out.println("chute-drive - Ready for deployment"); - System.out.println("Run: weevil deploy chute-drive"); - } -} diff --git a/src/main/java/robot/subsystems/.gitkeep b/src/main/java/robot/subsystems/.gitkeep deleted file mode 100644 index 5a7eeea..0000000 --- a/src/main/java/robot/subsystems/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# This file ensures the directory is tracked by git even when empty diff --git a/src/test/java/org/firstinspires/ftc/teamcode/subsystems/chute/ChuteTest.java b/src/test/java/org/firstinspires/ftc/teamcode/subsystems/chute/ChuteTest.java new file mode 100644 index 0000000..ca39c85 --- /dev/null +++ b/src/test/java/org/firstinspires/ftc/teamcode/subsystems/chute/ChuteTest.java @@ -0,0 +1,229 @@ +package org.firstinspires.ftc.teamcode.subsystems.chute; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Basic tests for chute system. + */ +public class ChuteTest { + + // Time step for simulation: 20ms = 50Hz update rate (mimics real robot loop) + private static final double DT = 0.02; + + // Tolerance for floating point comparisons (small acceptable error) + private static final double EPSILON = 0.01; + + /** + * Verify potentiometer wraps from pi back to 0 (sawtooth wave behavior). + * Tests that pot correctly simulates 0→pi→0→pi pattern. + */ + @Test + public void testPotWraps() { + MockPotentiometer pot = new MockPotentiometer(); + + pot.setPosition(0.0); + assertEquals(0.0, pot.getVoltage(), EPSILON); + + pot.setPosition(Math.PI / 2); + assertEquals(Math.PI / 2, pot.getVoltage(), EPSILON); + + // Wrap at pi + pot.setPosition(Math.PI); + assertEquals(0.0, pot.getVoltage(), EPSILON); + + pot.setPosition(2 * Math.PI); + assertEquals(0.0, pot.getVoltage(), EPSILON); + } + + /** + * Test homing sequence: drive down until pot stops changing, then capture home voltage. + * Verifies motor stops at home position with zero power. + */ + @Test + public void testHoming() { + Chute chute = new Chute(10.0); + + // Start somewhere + chute.getMotor().setPosition(0.5); + chute.getPot().setPosition(0.5); + + chute.home(); + + // Run until homed + for (int i = 0; i < 200; i++) { + chute.update(DT); + if (chute.isHomed()) break; + } + + assertTrue(chute.isHomed()); + assertEquals(0.0, chute.getMotor().getPower(), EPSILON); + } + + /** + * Test basic position control: home first, then move to target position. + * Verifies chute can accurately reach a commanded position. + */ + @Test + public void testMoveToPosition() { + Chute chute = new Chute(10.0); + + // Home first + chute.getMotor().setPosition(0.2); + chute.getPot().setPosition(0.2); + chute.home(); + + for (int i = 0; i < 200; i++) { + chute.update(DT); + if (chute.isHomed()) break; + } + + assertTrue(chute.isHomed(), "Should be homed"); + + // Move to target + chute.setTargetPosition(2.0); + + for (int i = 0; i < 1000; i++) { + chute.update(DT); + if (chute.isAtTarget()) break; + } + + assertTrue(chute.isAtTarget(), "Should reach target"); + assertTrue(Math.abs(chute.getPosition() - 2.0) < 0.3, + "Position should be close to 2.0, got: " + chute.getPosition()); + } + + /** + * Test unwrap logic through multiple pot wraps (3.5 rotations). + * Verifies controller correctly tracks absolute position across multiple pi boundaries. + */ + @Test + public void testMultipleRotations() { + Chute chute = new Chute(15.0); + + // Home + chute.getMotor().setPosition(0.2); + chute.getPot().setPosition(0.2); + chute.home(); + for (int i = 0; i < 200; i++) { + chute.update(DT); + if (chute.isHomed()) break; + } + + assertTrue(chute.isHomed(), "Should be homed"); + + // Move to 3.5 rotations (need more time) + double target = 3.5 * Math.PI; + chute.setTargetPosition(target); + + for (int i = 0; i < 2000; i++) { + chute.update(DT); + if (chute.isAtTarget()) break; + } + + // Should track through wraps + assertTrue(chute.getPosition() > 3.0 * Math.PI, + "Position should be > 3pi, got: " + chute.getPosition()); + assertTrue(Math.abs(chute.getPosition() - target) < 0.5, + "Position should be near target, expected: " + target + ", got: " + chute.getPosition()); + } + + /** + * Test reassembly scenario: pot has different voltage at home after disassembly. + * Verifies homing captures new reference voltage and positioning still works correctly. + */ + @Test + public void testReassembly() { + Chute chute = new Chute(10.0); + + // Pot at different voltage when at home + chute.getMotor().setPosition(0.0); + chute.getPot().setPosition(1.5); + + chute.home(); + for (int i = 0; i < 150; i++) { + chute.update(DT); + if (chute.isHomed()) break; + } + + assertTrue(chute.isHomed(), "Should be homed"); + + // Should still work + chute.setTargetPosition(2.0); + for (int i = 0; i < 1000; i++) { + chute.update(DT); + if (chute.isAtTarget()) break; + } + + assertTrue(chute.isAtTarget(), "Should reach target"); + assertTrue(Math.abs(chute.getPosition() - 2.0) < 0.3, + "Position should be close to 2.0, got: " + chute.getPosition()); + } + + /** + * Test maximum extension limit enforcement. + * Verifies chute stops at max extension even when commanded to go beyond. + */ + @Test + public void testMaxExtensionLimit() { + Chute chute = new Chute(2.0); // Only 2 radians max + + chute.getMotor().setPosition(0.1); + chute.getPot().setPosition(0.1); + chute.home(); + + for (int i = 0; i < 200; i++) { + chute.update(DT); + if (chute.isHomed()) break; + } + + // Try to go beyond max + chute.setTargetPosition(5.0); + + for (int i = 0; i < 1000; i++) { + chute.update(DT); + if (chute.isAtTarget()) break; + } + + // Should stop at max + assertTrue(chute.getPosition() <= 2.0, + "Should not exceed max: " + chute.getPosition()); + } + + /** + * Test bidirectional movement: move up, then back down. + * Verifies unwrap logic works correctly in both directions. + */ + @Test + public void testBidirectionalMovement() { + Chute chute = new Chute(10.0); + + chute.getMotor().setPosition(0.1); + chute.getPot().setPosition(0.1); + chute.home(); + + for (int i = 0; i < 200; i++) { + chute.update(DT); + if (chute.isHomed()) break; + } + + // Move up + chute.setTargetPosition(3.0); + for (int i = 0; i < 1000; i++) { + chute.update(DT); + if (chute.isAtTarget()) break; + } + double upPos = chute.getPosition(); + assertTrue(upPos > 2.5, "Should move up"); + + // Move back down + chute.setTargetPosition(1.0); + for (int i = 0; i < 1000; i++) { + chute.update(DT); + if (chute.isAtTarget()) break; + } + double downPos = chute.getPosition(); + assertTrue(downPos < 1.5, "Should move back down"); + assertTrue(downPos < upPos, "Down position should be less than up"); + } +} \ No newline at end of file diff --git a/src/test/java/robot/.gitkeep b/src/test/java/robot/.gitkeep deleted file mode 100644 index 5a7eeea..0000000 --- a/src/test/java/robot/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# This file ensures the directory is tracked by git even when empty diff --git a/src/test/java/robot/BasicTest.java b/src/test/java/robot/BasicTest.java deleted file mode 100644 index 1c3a932..0000000 --- a/src/test/java/robot/BasicTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package robot; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -class BasicTest { - @Test - void testBasic() { - assertTrue(true, "Basic test should pass"); - } -}