Trobotix 8696

Programming Site

< Back | < General Info

CENTERSTAGE

Final robot. Return of the LED lights. Robot on field.
Trobotix #2. Contour-based object detection with EOCV. First successful hanging test.

This season’s robot name is “Troballin”, but is referred to as Squid in the source code.
The mollusc library was jointly developed and used extensively this season.
League Tournament: Winning Alliance Captain and 1st Place Inspire Award
State: Rank #7 in Black Division (did not advance)


mollusc

See 6e446fa (basically pre-release v0.1.0).

Image Processing Pipelines

TotemPipeline
package org.firstinspires.ftc.teamcode.squid.delta.pipelines;

import org.firstinspires.ftc.teamcode.mollusc.Mollusc;
import org.firstinspires.ftc.teamcode.mollusc.vision.*;

import java.util.List;

import org.openftc.easyopencv.OpenCvPipeline;

import org.opencv.core.Scalar;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Mat;

public class TotemPipeline extends OpenCvPipeline {

    // Note: Red hues wrap around the hue color wheel.
    public Scalar lowerRed = new Scalar(0, 95, 95);
    public Scalar upperRed = new Scalar(10, 255, 255);
    public Scalar lowerRed2 = new Scalar(160, 95, 95);
    public Scalar upperRed2 = new Scalar(180, 255, 255);
    public Scalar lowerBlue = new Scalar(100, 80, 80);
    public Scalar upperBlue = new Scalar(130, 255, 255);

    private ColorRange redColorRange = new ColorRange(lowerRed, upperRed);
    private ColorRange redColorRange2 = new ColorRange(lowerRed2, upperRed2);
    private ColorRange blueColorRange = new ColorRange(lowerBlue, upperBlue);

    private Point p1 = new Point(0, 0), p2 = new Point(110, 240);
    private Point p3 = new Point(110, 0), p4 = new Point(320, 240);
    private Rect r1 = new Rect (p1, p2);
    private Rect r2 = new Rect (p3, p4);

    private ColorRange[] focusRange;
    private Alliance alliance;
    private List<VisionObject> objs = null;

    public double threshold = 0.025;
    public boolean calibrate = true;

    public TotemPipeline(Alliance alliance) {
        this.alliance = alliance;
        if (alliance == Alliance.RED) {
            focusRange = new ColorRange[] {redColorRange, redColorRange2};
        } else {
            focusRange = new ColorRange[] {blueColorRange};
        }
    }

    public void setBound(Scalar bound, int idx, double value) {
        bound.val[idx] = value;
    }

    @Override
    public void init(Mat firstFrame) {
    }

    @Override
    public Mat processFrame(Mat inputFrame) {
        objs = ObjectDetector.coloredObjectCoordinates(
            inputFrame, 
            threshold, 
            0.0, 
            calibrate, 
            focusRange
        );
        if (calibrate) {
            ObjectDetector.rectangle(
                inputFrame, 
                (int)p1.x, 
                (int)p1.y, 
                (int)p2.x, 
                (int)p2.y, 
                ObjectDetector.BLUE_COLOR, 2
            );
        }
        return inputFrame;
    }

    public TotemZone getZone() {
        if (objs == null || objs.isEmpty()) {
            return TotemZone.RIGHT;
        } else { // This may help mitigate the exceedingly rare edge case where the image processing thread clears `objs` after the prior check.
            VisionObject largest = objs.get(0);
            for (VisionObject obj : objs) {
                if (obj.pixelTotality > largest.pixelTotality) {
                    largest = obj;
                }
            }
            if (largest.x > p2.x) {
                return TotemZone.CENTER;
            }
            return TotemZone.LEFT;
        }
    }

    public enum Alliance {
        RED, BLUE
    }
    public enum TotemZone {
        LEFT, CENTER, RIGHT
    }
}

Configuration

SquidWare
package org.firstinspires.ftc.teamcode.squid.delta;

import org.firstinspires.ftc.teamcode.squid.delta.subsystems.*;
import org.firstinspires.ftc.teamcode.mollusc.auto.odometry.*;
import org.firstinspires.ftc.teamcode.mollusc.drivetrain.*;
import org.firstinspires.ftc.teamcode.mollusc.wrapper.*;
import org.firstinspires.ftc.teamcode.mollusc.utility.*;
import org.firstinspires.ftc.teamcode.mollusc.Mollusc;

// import com.qualcomm.robotcore.hardware.TouchSensor;
import com.qualcomm.robotcore.hardware.DistanceSensor;
import com.qualcomm.robotcore.hardware.DcMotorEx;
import com.qualcomm.robotcore.hardware.CRServo;
import com.qualcomm.robotcore.hardware.DcMotorSimple;
import com.qualcomm.robotcore.hardware.Servo;
import com.qualcomm.robotcore.hardware.IMU;

import com.qualcomm.hardware.rev.RevHubOrientationOnRobot;

public class SquidWare {

    public Configuration config;

    public DrivetrainBaseFourWheel base;

    // Deadwheels.
    public DeadWheels deadWheels;

    public Intake intake = new Intake();
    public Conveyor conveyor = new Conveyor();
    public Scoring scoring = new Scoring();
    public Launcher launcher = new Launcher();
    public Lift lift = new Lift();

    public IMU imu;

    public SquidWare(Configuration config, boolean resetIMU) throws Exception {
        this.config = config;

        intake.UP_1 = intake.intakePos1 = config.getDouble("intake servo up 1");
        intake.UP_2 = intake.intakePos2 = config.getDouble("intake servo up 2");
        intake.DOWN_1 = config.getDouble("intake servo down 1");
        intake.DOWN_2 = config.getDouble("intake servo down 2");
        intake.MAX_DOWN_1 = config.getDouble("intake servo 1 max down");
        intake.MAX_DOWN_2 = config.getDouble("intake servo 2 max down");
        intake.DELTA = config.getDouble("intake servo delta");
        intake.PRESET_POWER = config.getDouble("intake power");

        conveyor.PRESET_POWER = config.getDouble("conveyor power");
        conveyor.DISTANCE_THRESHOLD = config.getDouble("pixel distance threshold cm");
        conveyor.PIXEL_TIME = config.getDouble("pixel time seconds");
        conveyor.CARRY_TIME = config.getDouble("pixel carry time seconds");
        
        scoring.SPINNER_POWER = config.getDouble("spinning servo powers");
        scoring.SLIDE_MIN = config.getInteger("slide min");
        scoring.SLIDE_MAX = config.getInteger("slide max");
        scoring.RELEASE_PIXEL = config.getDouble("release go");
        scoring.RELEASE_RESET = config.getDouble("release reset");
        scoring.ANGLER_DELTA = config.getDouble("angling servo delta");

        launcher.FIRE = config.getDouble("launcher fire");

        lift.POWER = config.getDouble("lift power");

        Make make = new Make();

        imu = make.imu(
            "imu",
            RevHubOrientationOnRobot.LogoFacingDirection.LEFT,
            RevHubOrientationOnRobot.UsbFacingDirection.BACKWARD,
            resetIMU
        );

        base = new DrivetrainBaseFourWheel(
            make.motor("frontLeft", getStringConfigDirection("frontLeft direction").motor), 
            make.motor("frontRight", getStringConfigDirection("frontRight direction").motor), 
            make.motor("rearLeft", getStringConfigDirection("rearLeft direction").motor), 
            make.motor("rearRight", getStringConfigDirection("rearRight direction").motor)
        );

        intake.servo1 = make.servo("intakeServo1", getStringConfigDirection("intake servo 1 direction").servo);
        intake.servo2 = make.servo("intakeServo2", getStringConfigDirection("intake servo 2 direction").servo);
        intake.motor = make.motor("intake", getStringConfigDirection("intake motor direction").motor);
        
        conveyor.motor = make.motor("conveyor", getStringConfigDirection("conveyor motor direction").motor);
        conveyor.sensor = Mollusc.opMode.hardwareMap.get(DistanceSensor.class, "pixelSensor");

        scoring.angler = make.servo("angler", getStringConfigDirection("angling servo direction").servo);
        scoring.slide = make.motor("slide", getStringConfigDirection("slide motor direction").motor);
        scoring.slideEncoder = new Encoder(scoring.slide, 1, 1, 1);
        // scoring.limitSwitch = Mollusc.opMode.hardwareMap.get(TouchSensor.class, "limitSwitch");
        scoring.spinner1 = make.crservo("spinner1", getStringConfigDirection("spinner servo 1 direction").crservo);
        scoring.spinner2 = make.crservo("spinner2", getStringConfigDirection("spinner servo 2 direction").crservo);
        scoring.stopper = make.servo("release", getStringConfigDirection("release servo direction").servo);

        launcher.servo = make.crservo("launcher", CRServo.Direction.FORWARD);

        int TPR = config.getInteger("TPR");
        DcMotorEx leftEncoderMotor = make.motor("leftEncoder", DcMotorEx.Direction.FORWARD);
        Encoder leftEncoder = new Encoder(leftEncoderMotor, -1.0, TPR, 35);
        Encoder rightEncoder = new Encoder(intake.motor, 1.0, TPR, 35);
        Encoder centerEncoder = new Encoder(conveyor.motor, 1.0, TPR, 35);
        deadWheels = new DeadWheels(
            new Pose(0, 0, Math.PI),
            leftEncoder, rightEncoder, centerEncoder, 
            -352.435, 30
        );

        // lift.motor = make.motor("lift", DcMotorEx.Direction.FORWARD);
        lift.motor = leftEncoderMotor;
    }

    public DirectionGroup getStringConfigDirection(String key) throws Exception {
        String direction = config.getString(key);
        return new DirectionGroup(direction);
    }

    public class DirectionGroup {
        public DcMotorEx.Direction motor;
        public Servo.Direction servo;
        public CRServo.Direction crservo;

        public DirectionGroup(String direction) throws Exception {
            if (!direction.equals("forward") && !direction.equals("reverse")) {
                throw new Exception("Invalid motor direction value: " + direction);
            }
            if (direction.equals("forward")) {
                this.motor = DcMotorEx.Direction.FORWARD;
                this.servo = Servo.Direction.FORWARD;
                this.crservo = CRServo.Direction.FORWARD;
            } else {
                this.motor = DcMotorEx.Direction.REVERSE;
                this.servo = Servo.Direction.REVERSE;
                this.crservo = CRServo.Direction.REVERSE;
            }
        }
    }
}
delta [TXT]
// Configuration File

field centric on: true

autonomous drive power: 0.5

drive power max: 0.87
strafe power max: 0.97
turn power max: 0.87
drive power max reduced: 0.6
strafe power max reduced: 0.7
turn power max reduced: 0.6

frontLeft direction:  reverse
frontRight direction: forward
rearLeft direction:   reverse
rearRight direction:  forward

TPR: 8192

intake servo 1 direction: forward
intake servo 2 direction: forward
intake motor direction: forward

conveyor motor direction: reverse
pixel distance threshold cm: 5
pixel time seconds: 0.89
pixel carry time seconds: 0.2

angling servo direction: forward
slide motor direction: reverse
spinner servo 1 direction: forward
spinner servo 2 direction: forward
release servo direction: forward

intake servo up 1: 0.52
intake servo up 2: 0.70
intake servo down 1: 0.67
intake servo down 2: 0.55
intake servo 1 max down: 0.69
intake servo 2 max down: 0.53
intake servo delta: 0.01
intake power: 0.68

conveyor power: 1
spinning servo powers: 1
slide min: 10
slide max: 2050
angling servo delta: 0.015
release go: 0.35
release reset: 0.63

launcher fire: 1

lift power: -1

Subsystems

Conveyor
package org.firstinspires.ftc.teamcode.squid.delta.subsystems;

import com.qualcomm.robotcore.hardware.DistanceSensor;
import com.qualcomm.robotcore.hardware.DcMotorEx;

import com.qualcomm.robotcore.util.ElapsedTime;

import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit;
import org.firstinspires.ftc.teamcode.mollusc.Mollusc;

public class Conveyor {

    public DcMotorEx motor;
    public DistanceSensor sensor;
    public ElapsedTime timer = new ElapsedTime();

    public double PRESET_POWER;
    // PIXEL_TIME --> time until sensor begins searching for pixels again.
    // CARRY_TIME --> time until the conveyor is switched to manual / stop mode after detecting the second pixel.
    public double DISTANCE_THRESHOLD, PIXEL_TIME, CARRY_TIME;

    public DetectionState detectionState = DetectionState.NONE;

    public void power(double power) {
        motor.setPower(power);
    }

    public boolean pixelOverflow() {
        boolean sensorState = sensor.getDistance(DistanceUnit.CM) <= DISTANCE_THRESHOLD;

        switch (detectionState) {
            case NONE:
                if (sensorState && timer.seconds() >= PIXEL_TIME) {
                    detectionState = DetectionState.FIRST_PIXEL;
                    timer.reset();
                }
                break;
            case FIRST_PIXEL:
                if (sensorState && timer.seconds() >= PIXEL_TIME) {
                    detectionState = DetectionState.SECOND_PIXEL;
                    timer.reset();
                }
                break;
            case SECOND_PIXEL:
                if (timer.seconds() >= CARRY_TIME) {
                    return true;
                }
                break;
        }
        return false;
    }

    public void resetState() {
        detectionState = DetectionState.NONE;
        timer.reset();
    }

    public enum DetectionState {
        NONE, FIRST_PIXEL, SECOND_PIXEL
    }
}
Intake
package org.firstinspires.ftc.teamcode.squid.delta.subsystems;

import com.qualcomm.robotcore.hardware.DcMotorEx;
import com.qualcomm.robotcore.hardware.Servo;

public class Intake {

    public Servo servo1, servo2;
    public DcMotorEx motor;

    public double UP_1, UP_2;
    public double DOWN_1, DOWN_2;
    public double DELTA;
    public double MAX_DOWN_1, MAX_DOWN_2;
    public double PRESET_POWER;
    public double intakePos1, intakePos2;

    public void presetUp() {
        position(UP_1, UP_2);
    }

    public void presetDown() {
        position(DOWN_1, DOWN_2);
    }

    public void up() {
        position(intakePos1 - DELTA, intakePos2 + DELTA);
    }

    public void down() {
        position(intakePos1 + DELTA, intakePos2 - DELTA);
    }

    public void position(double pos1, double pos2) {
        if (pos1 >= UP_1 && pos1 <= MAX_DOWN_1) {
            intakePos1 = pos1;
            servo1.setPosition(intakePos1);
        }
        if (pos2 <= UP_2 && pos2 >= MAX_DOWN_2) {
            intakePos2 = pos2;
            servo2.setPosition(intakePos2);
        }
    }

    public void power(double power) {
        motor.setPower(power);
    }
}
Launcher
package org.firstinspires.ftc.teamcode.squid.delta.subsystems;

import com.qualcomm.robotcore.hardware.CRServo;

public class Launcher {

    public CRServo servo;

    public double FIRE;

    public void fire() {
        servo.setPower(FIRE);
    }
    public void stop() {
        servo.setPower(0);
    }
}
Lift
package org.firstinspires.ftc.teamcode.squid.delta.subsystems;

import com.qualcomm.robotcore.hardware.DcMotorEx;

public class Lift {

    public DcMotorEx motor;

    public double POWER;

    public void power(double power) {
        motor.setPower(power);
    }
}
Scoring
package org.firstinspires.ftc.teamcode.squid.delta.subsystems;

import org.firstinspires.ftc.teamcode.mollusc.wrapper.Encoder;

// import com.qualcomm.robotcore.hardware.TouchSensor;
import com.qualcomm.robotcore.hardware.DcMotorEx;
import com.qualcomm.robotcore.hardware.CRServo;
import com.qualcomm.robotcore.hardware.Servo;

public class Scoring {

    public Servo angler;
    public DcMotorEx slide;
    public Encoder slideEncoder;
    // public TouchSensor limitSwitch;
    public CRServo spinner1, spinner2;
    public Servo stopper;

    public double SPINNER_POWER;
    public double RELEASE_PIXEL, RELEASE_RESET;
    public int SLIDE_MIN, SLIDE_MAX;
    public double ANGLER_DELTA;

    public double anglerPos = 1.0;
    
    public void slidePower(double power) {
        if (slide.isOverCurrent()
            || (power > 0 && slideEncoder.getTicks() > SLIDE_MAX)
            || (power < 0 && slideEncoder.getTicks() < SLIDE_MIN)
            /*|| (power < 0 && limitSwitch.isPressed())*/) {
            slide.setPower(0);
            return;
        }
        slide.setPower(power);
    }

    public void spinningOn() {
        spinner1.setPower(-SPINNER_POWER);
        spinner2.setPower(-SPINNER_POWER);
    }

    public void spinningOff() {
        spinner1.setPower(0);
        spinner2.setPower(0);
    }

    public void spinningPanic() {
        spinner1.setPower(SPINNER_POWER);
        spinner2.setPower(SPINNER_POWER);
    }

    public void anglerUp() {
        anglerPosition(anglerPos + ANGLER_DELTA);
    }
    public void anglerDown() {
        anglerPosition(anglerPos - ANGLER_DELTA);
    }
    public void anglerPosition(double pos) {
        if (0 <= pos && pos <= 1) {
            anglerPos = pos;
            angler.setPosition(anglerPos);
        }
    }

    public void releasePixel() {
        stopper.setPosition(RELEASE_PIXEL);
    }

    public void releaseReset() {
        stopper.setPosition(RELEASE_RESET);
    }
}

Autonomous

AutoSquidDelta
/*
AutoSquidDelta

Main robot.
*/

package org.firstinspires.ftc.teamcode.squid.delta;

import org.firstinspires.ftc.teamcode.squid.delta.pipelines.TotemPipeline;

import org.firstinspires.ftc.teamcode.mollusc.auto.interpreter.*;
import org.firstinspires.ftc.teamcode.mollusc.utility.*;
import org.firstinspires.ftc.teamcode.mollusc.wrapper.*;
import org.firstinspires.ftc.teamcode.mollusc.Mollusc;
import org.firstinspires.ftc.teamcode.mollusc.auto.*;

import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;

import org.openftc.easyopencv.OpenCvCameraRotation;
import org.openftc.easyopencv.OpenCvCameraFactory;
import org.openftc.easyopencv.OpenCvCamera;

@Autonomous(name="AutoSquidDelta III-I", group="Squid")

public class AutoSquidDelta extends MolluscLinearOpMode {

    private Configuration config;

    private Auto auto;
    private SquidWare squidWare;
    private Interpreter interpreter;
    private TotemPipeline totemPipeline;

    private boolean alliance = true, route = true;

    @Override
    public void molluscRunOpMode() {
        telemetry.setAutoClear(false);

        Configuration.handle(() -> {
            config = new Configuration("delta.txt");
            alliance = Configuration.inputBoolean("Alliance", "Red", "Blue", alliance);
            route = Configuration.inputBoolean("Route", "Short", "Long", route);
            squidWare = new SquidWare(config, true);
            interpreter = new Interpreter(new Asset("script_delta.txt"));

            register(); // Important.

            double maxDrivePower = config.getDouble("autonomous drive power");
            auto = new MecanumAutoII(
                squidWare.base, 
                squidWare.deadWheels, 
                interpreter, 
                new PIDF(0.005, 0, 0, 0.15, 0.25, 1),
                new PIDF(0.009, 0, 0, 0.2, 0.25, 1),
                new PIDF(1.4, 0.00, 0.00, 0.15, 0.25, 1),
                maxDrivePower, 
                30,
                6
            );
            auto.register();
        });

        telemetry.setAutoClear(true);

        int cameraMonitorViewId = hardwareMap.appContext.getResources().getIdentifier("cameraMonitorViewId", "id", hardwareMap.appContext.getPackageName());
        WebcamName webcamName = hardwareMap.get(WebcamName.class, "Webcam 1");
        OpenCvCamera webcam = OpenCvCameraFactory.getInstance().createWebcam(webcamName, cameraMonitorViewId);

        webcam.openCameraDeviceAsync(new OpenCvCamera.AsyncCameraOpenListener() {
            @Override
            public void onOpened() {
                webcam.startStreaming(320, 240, OpenCvCameraRotation.UPRIGHT);
            }
            @Override
            public void onError(int errorCode) {
            }
        });
        totemPipeline = new TotemPipeline(alliance ? TotemPipeline.Alliance.RED : TotemPipeline.Alliance.BLUE);
        webcam.setPipeline(totemPipeline);

        PIDF.bulkMode();

        // Pre-start manual image processing calibrations.
        while (!gamepad1.dpad_up && !isStopRequested()) {
            telemetry.addLine("Press dpad up to confirm.");
            telemetry.addLine("(A) --> Configure threshold.");
            telemetry.addLine("(X) --> Configure Red 1.");
            telemetry.addLine("(Y) --> Configure Red 2.");
            telemetry.addLine("(B) --> Configure Blue.");
            displayTotemZone();
            telemetry.update();

            if (gamepad1.a) {
                configDoubleNonBlocking(totemPipeline.threshold, 0.001, (double value) -> {
                    totemPipeline.threshold = value;
                    telemetry.addData("Threshold", value);
                });
            } else if (gamepad1.x) {
                Configuration.handle(() -> {
                    boolean ub = Configuration.inputBoolean("Bound", "Upper", "Lower", true);
                    int idx = Configuration.inputInteger("Value", 0, 0.25, 1, new String[] {"Hue", "Saturation", "Brightness"});
                    if (ub) {
                        configDoubleNonBlocking(totemPipeline.upperRed.val[idx], 1, (double value) -> {
                            totemPipeline.setBound(totemPipeline.upperRed, idx, value);
                            telemetry.addData("Upper Red Color Bound", totemPipeline.upperRed);
                        });
                    } else {
                        configDoubleNonBlocking(totemPipeline.lowerRed.val[idx], 1, (double value) -> {
                            totemPipeline.setBound(totemPipeline.lowerRed, idx, value);
                            telemetry.addData("Lower Red Color Bound", totemPipeline.lowerRed);
                        });
                    }
                });
            } else if (gamepad1.y) {
                Configuration.handle(() -> {
                    boolean ub = Configuration.inputBoolean("Bound", "Upper", "Lower", true);
                    int idx = Configuration.inputInteger("Value", 0, 0.25, 1, new String[] {"Hue", "Saturation", "Brightness"});
                    if (ub) {
                        configDoubleNonBlocking(totemPipeline.upperRed2.val[idx], 1, (double value) -> {
                            totemPipeline.setBound(totemPipeline.upperRed2, idx, value);
                            telemetry.addData("Upper Red Color Bound 2", totemPipeline.upperRed2);
                        });
                    } else {
                        configDoubleNonBlocking(totemPipeline.lowerRed2.val[idx], 1, (double value) -> {
                            totemPipeline.setBound(totemPipeline.lowerRed2, idx, value);
                            telemetry.addData("Lower Red Color Bound 2", totemPipeline.lowerRed2);
                        });
                    }
                });
            } else if (gamepad1.b) {
                Configuration.handle(() -> {
                    boolean ub = Configuration.inputBoolean("Bound", "Upper", "Lower", true);
                    int idx = Configuration.inputInteger("Value", 0, 0.25, 1, new String[] {"Hue", "Saturation", "Brightness"});
                    if (ub) {
                        configDoubleNonBlocking(totemPipeline.upperBlue.val[idx], 1, (double value) -> {
                            totemPipeline.setBound(totemPipeline.upperBlue, idx, value);
                            telemetry.addData("Upper Blue Color Bound", totemPipeline.upperBlue);
                        });
                    } else {
                        configDoubleNonBlocking(totemPipeline.lowerBlue.val[idx], 1, (double value) -> {
                            totemPipeline.setBound(totemPipeline.lowerBlue, idx, value);
                            telemetry.addData("Lower Blue Color Bound", totemPipeline.lowerBlue);
                        });
                    }
                });
            }

            idle();
        }

        totemPipeline.calibrate = false;
        telemetry.addLine("Waiting for start.");
        telemetry.update();

        waitForStart();

        webcam.pauseViewport();

        Configuration.handle(() -> {
            interpreter.run(true);
        });
    }

    private void register() {
        interpreter.register("GoToTotemSection", (Object[] args) -> {
            String delimiter = alliance ? "Red" : "Blue";
            switch (totemPipeline.getZone()) {
                case CENTER:
                    interpreter.advanceTo("SectionTotemCenter" + delimiter);
                    break;
                case LEFT:
                    interpreter.advanceTo("SectionTotemLeft" + delimiter);
                    break;
                case RIGHT:
                    interpreter.advanceTo("SectionTotemRight" + delimiter);
                    break;
            }
        });
        interpreter.register("SectionTotemCenterRed", Interpreter.noneAction);
        interpreter.register("SectionTotemLeftRed", Interpreter.noneAction);
        interpreter.register("SectionTotemRightRed", Interpreter.noneAction);
        interpreter.register("SectionTotemCenterBlue", Interpreter.noneAction);
        interpreter.register("SectionTotemLeftBlue", Interpreter.noneAction);
        interpreter.register("SectionTotemRightBlue", Interpreter.noneAction);

        interpreter.register("GoToShortOrLongRoute", (Object[] args) -> {
            if (route) {
                interpreter.advanceTo("ShortRoute");
            } else {
                interpreter.advanceTo("LongRoute");
            }
        });
        interpreter.register("ShortRoute", Interpreter.noneAction);
        interpreter.register("LongRoute", Interpreter.noneAction);

        interpreter.register("intake", (Object[] args) -> {
            switch ((String)args[0]) {
                case "up":
                    squidWare.intake.presetUp();
                    break;
                case "down":
                    squidWare.intake.presetDown();
                    break;
                case "on":
                    squidWare.intake.power(squidWare.intake.PRESET_POWER);
                    break;
                case "reverse":
                    squidWare.intake.power(-squidWare.intake.PRESET_POWER);
                    break;
                case "off":
                    squidWare.intake.power(0);
                    break;
            }
        }, String.class);
        
        interpreter.register("itsPower", (Object[] args) -> {
            squidWare.conveyor.power((Double)args[0]);
        }, Double.class);
        interpreter.register("slidePower", (Object[] args) -> {
            squidWare.scoring.slidePower((Double)args[0]);
            interpreter.getActions().get("wait_seconds-f").execute(new Object[] {args[1]});
            squidWare.scoring.slidePower(0);
        }, Double.class, Double.class);

        interpreter.register("setAnglerPos", (Object[] args) -> {
            squidWare.scoring.anglerPosition((Double)args[0]);
        }, Double.class);

        interpreter.register("scoring", (Object[] args) -> {
            switch ((String)args[0]) {
                case "release":
                    squidWare.scoring.releasePixel();
                    break;
                case "close":
                    squidWare.scoring.releaseReset();
                    break;
            }
        }, String.class);

        interpreter.register("pause", (Object[] args) -> {
            while (!gamepad1.a && !isStopRequested()) {
                idle();
            }
        });

        interpreter.register("GoToEnd", (Object[] args) -> {
            interpreter.advanceTo("END");
        });
        interpreter.register("END", Interpreter.noneAction);
    }

    public void displayTotemZone() {
        telemetry.addData("Totem Zone", totemPipeline.getZone());
    }

    private void configDoubleNonBlocking(double value, double delta, Synchronous synchronous) {
        while (gamepad1.a && !isStopRequested()) {
            idle();
        }
        while (!gamepad1.a && !isStopRequested()) {
            if (gamepad1.dpad_up) {
                value += delta;
                sleep(250);
            } else if (gamepad1.dpad_down) {
                value -= delta;
                sleep(250);
            } else if (gamepad1.dpad_left) {
                delta /= 10;
                sleep(250);
            } else if (gamepad1.dpad_right) {
                delta *= 10;
                sleep(250);
            }
            synchronous.process(value);
            displayTotemZone();
            telemetry.addLine("Press (A) to confirm.");
            telemetry.addData("Value", value);
            telemetry.addData("Delta", delta);
            telemetry.update();
            idle();
        }
        while (gamepad1.a && !isStopRequested()) {
            idle();
        }
    }

    @FunctionalInterface
    private interface Synchronous {
        void process(double value);
    }
}
script_delta [TXT]
// Timeout seconds: seconds before a move times out and proceeds to the next instruction.
// Static timeout milliseconds: milliseconds during which, if the algorithms determines that the robot
//                              is in the right pose based on tolerances, it will end the move.
set_timeout_seconds 5.0
set_static_timeout_milliseconds 50

// Drive command: x / forward axis, y / horizontal axis, heading in degrees
// Example: drive 1000 0 180 --> x = 100, y = 0, z = 180 deg.

intake down
setAnglerPos 1.0
wait_seconds 1.5

// BEGIN ISOLATED TESTING

// Note: use the command "pause" to pause the autonomous at that point until gamepad1.a.

// Run some isolated tests here.
//GoToEnd
// END ISOLATED TESTING

GoToShortOrLongRoute

ShortRoute
    GoToTotemSection

    SectionTotemCenterRed
        drive 800	-8.72	178.7628321
        intake up
        itsPower -1.0
        wait_seconds 0.5
        itsPower 0.0
        drive 543.91	-4.69	178.7628321
        drive 656.57	641.65	171.3143807
        drive 625.13	674.22	-89.95437384
        drive 626.45	956.53	-89.95437384
        wait_seconds 0.3
        setAnglerPos 0.0
        wait_seconds 0.9
        scoring release
        wait_seconds 2.0
        drive 626 890 -89.95
        drive 60	850	-89.95437384
        GoToEnd

    SectionTotemLeftRed
        drive 397.34	6.72	177.6169165
        drive 405.06	32.17	-146.6771956
        drive 686.08	-151.03	-128.3425461
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 556.9	172.35	-136.3639552
        drive 630.12	165.71	-91.67324722
        drive 750.7	980.25	-94.5380362
        wait_seconds 0.3
        setAnglerPos 0.0
        wait_seconds 0.9
        scoring release
        wait_seconds 2.0
        drive 751	890.68	-94.50282517
        drive -3.3	871.68	-97.40282517
        GoToEnd

    SectionTotemRightRed
        drive 325.3	269.5	179.3357899
        drive 656.65	272.33	178.7628321
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 301.27	263.74	178.1898743
        drive 364.58	294.51	-92.24620502
        drive 484.5	1006.79	-91.67324722
        wait_seconds 0.3
        setAnglerPos 0.0
        wait_seconds 0.9
        scoring release
        wait_seconds 2.0
        drive 484.58 950 -91.67
        drive 48.98	910.33	-92.24620502
        GoToEnd

    SectionTotemCenterBlue
        drive 807.01	-3.07	-179.3357899
        intake up
        itsPower -1.0
        wait_seconds 0.5
        itsPower 0.0
        drive 420.1	2.18	-178.7628321
        drive 688.16	-1014	92.24620502
        wait_seconds 0.3
        setAnglerPos 0.0
        wait_seconds 0.9
        scoring release
        wait_seconds 2.0
        drive 688.16 -950 92.25
        drive 43.38	-903.21	93.9650784
        GoToEnd

    SectionTotemLeftBlue
        drive 402.16	-261.46	-177.6169165
        drive 661.23	-273.34	-178.1898743
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 309.23	-258.59	-177.6169165
        drive 481.18	-985.84	92.81916281
        wait_seconds 0.3
        setAnglerPos 0.0
        wait_seconds 0.9
        scoring release
        wait_seconds 2.0
        drive 481.18 -950 92.82
        drive 34.27	-910	93.9650784
        GoToEnd

    SectionTotemRightBlue
        drive 475.96	6.47	-179.3357899
        drive 467.6	27.7	148.3960689
        drive 686.7	173.08	139.801702
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 472.14	-132.4	140.9476176
        drive 812.57	-1001.28	93.9650784
        wait_seconds 0.3
        setAnglerPos 0.0
        wait_seconds 0.9
        scoring release
        wait_seconds 2.0
        drive 812.57 -950 93.965
        drive 43.06	-914.46	90.52733163
        GoToEnd

LongRoute
    GoToTotemSection

    SectionTotemCenterRed
        drive 792.02	-27.63	-178.7628321
        intake up
        itsPower -1.0
        wait_seconds 0.5
        itsPower 0.0
        drive 525.96	-0.98	-178.7628321
        drive 514.4	-392.49	179.9087477
        drive 1363.54	-390.8	-178.7628321
        drive 1341.46	-481.21	90.52733163
        setAnglerPos 0.0
        wait_seconds 0.5
        drive 1404.45	1807.39	85.37071147
        drive 760.09	1800.26	91.67324722
        drive 732.16	1885.96	-90.52733163
        drive 722.5	2189.4	-91.67324722
        scoring release
        wait_seconds 2.0
        drive 722.5 2100 -91.67
        drive 1321.6	2179.86	-93.9650784
        GoToEnd

    SectionTotemLeftRed
        drive 341.75	-239.31	-177.6169165
        drive 661.65	-243.3	-178.1898743
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 367.2	-251.73	-177.6169165
        drive 371.79	26.82	-178.7628321
        drive 1255.44	3.24	-179.3357899
        drive 1298.4	-28.39	92.24620502
        setAnglerPos 0.0
        wait_seconds 0.5
        drive 1382.77	1901.32	90.52733163
        drive 753.79	1984.12	90.52733163
        drive 880.15	2135.31	-91.10028943
        drive 880	2216.99	-91.67324722
        scoring release
        wait_seconds 2.0
        drive 880 2180 -91.67
        drive 1394.92	2123.56	-92.81916281
        GoToEnd

    SectionTotemRightRed
        drive 740.51	-322.67	91.100
        drive 760.79 92.98 91.673
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 714.64 -378.83 91.100
        drive 1391.31 -253.43 91.08958
        setAnglerPos 0.0
        wait_seconds 0.5
        drive 1366.08 1924.05 88.235500
        drive 790 1977.66 -95.684
        drive 690 2150.96 -95.68
        scoring release
        wait_seconds 2.0
        drive 690 2090 -95.68
        drive 1372.83 2070.38 -97.976
        GoToEnd

    SectionTotemCenterBlue
        drive 825.53 16.56 -178.19
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 417.69 38.18 -176.47
        drive 411.11 440.22 -177.04
        drive 1284.78 408.61 179.91
        drive 1348.43 330.31 -91.100
        setAnglerPos 0.0
        wait_seconds 0.5
        drive 1367.78 -1951.86 -89.38
        drive 797.3 -2022.37 86.52
        drive 817.99 -2185.11 85.944
        scoring release
        wait_seconds 2.0
        drive 817.99 -2100 85.944
        drive 1370.94 -2083.26 87.09
        GoToEnd

    SectionTotemLeftBlue
        drive 756.56 254.07 -91.67
        drive 777.56 -88.87 -91.67
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 752.78 378.4 -89.95
        drive 1329.63 390.62 -93.965
        setAnglerPos 0.0
        wait_seconds 0.5
        drive 1414.41 -1905.87 -92.246
        drive 743.44 -1963.94 84.798
        drive 758.97 -2182.25 84.978
        scoring release
        wait_seconds 2.0
        drive 758.97 -2100 84.978
        drive 1424.54 -2039.83 83.651
        GoToEnd

    SectionTotemRightBlue
        drive 443.72 282.42 179.336
        drive 688.39 277.46 178.19
        intake up
        itsPower -1.0
        wait_seconds 1.0
        itsPower 0.0
        drive 398.47 274.36 178.76
        drive 387.53 -34.25 179.33
        drive 1257.17 0.89 178.76
        drive 1254.31 30.11 -92.2462
        setAnglerPos 0.0
        wait_seconds 0.5
        drive 1300.43 -1892.43 -91.67
        drive 930.69 -1922.98 84.80
        drive 950.18 -2204.24 85.371
        scoring release
        wait_seconds 2.0
        drive 950.18 -2150 85.371
        drive 1390.62 -2045.26 86.5166
        GoToEnd

END

TeleOp

SquidDelta
/*
SquidDelta

Main robot.
*/

package org.firstinspires.ftc.teamcode.squid.delta;

import org.firstinspires.ftc.teamcode.mollusc.drivetrain.*;
import org.firstinspires.ftc.teamcode.mollusc.utility.*;
import org.firstinspires.ftc.teamcode.mollusc.wrapper.*;
import org.firstinspires.ftc.teamcode.squid.delta.subsystems.Conveyor;
import org.firstinspires.ftc.teamcode.mollusc.Mollusc;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;

@TeleOp(name="SquidDelta III-I", group="Squid")

public class SquidDelta extends MolluscLinearOpMode {

    private Configuration config;

    private Drivetrain drivetrain;
    private SquidWare squidWare;

    private MecanumFieldCentric fieldCentricDrive;
    private MecanumRobotCentric robotCentricDrive;
    private double maxDrive, maxStrafe, maxTurn;
    private double maxDriveReduced, maxStrafeReduced, maxTurnReduced;
    private double drive, strafe, turn;

    private UpDownState intakeState = UpDownState.UP;
    private SystemState intakeMotorState = SystemState.OFF;
    private SystemState conveyorMotorState = SystemState.OFF;
    private SystemState spinningState = SystemState.OFF;
    private UpDownState launchState = UpDownState.DOWN;
    private SystemState gState = SystemState.OFF;
    private SystemState reducedDrivePowerState = SystemState.OFF;

    @Override
    public void molluscRunOpMode() {
        telemetry.setAutoClear(false);

        Configuration.handle(() -> {
            config = new Configuration("delta.txt");
            boolean fieldCentric = config.getBoolean("field centric on");
            maxDrive = config.getDouble("drive power max");
            maxStrafe = config.getDouble("strafe power max");
            maxTurn = config.getDouble("turn power max");
            maxDriveReduced = config.getDouble("drive power max reduced");
            maxStrafeReduced = config.getDouble("strafe power max reduced");
            maxTurnReduced = config.getDouble("turn power max reduced");

            squidWare = new SquidWare(config, false);

            fieldCentricDrive = new MecanumFieldCentric(squidWare.base, squidWare.imu);
            fieldCentricDrive.setDriveParams(maxDrive, maxStrafe, maxTurn, Math.PI);
            robotCentricDrive = new MecanumRobotCentric(squidWare.base) {
                @Override
                public void drive(double drive, double strafe, double turn) {
                    super.drive(-drive, -strafe, turn); // Because it starts backwards.
                }
            };
            robotCentricDrive.setDriveParams(maxDrive, maxStrafe, maxTurn);
            if (fieldCentric) {
                drivetrain = fieldCentricDrive;
                telemetry.speak("Driving field centric.");
            } else {
                drivetrain = robotCentricDrive;
                telemetry.speak("Driving robot centric.");
            }
        });

        telemetry.setAutoClear(true);

        waitForStart();

        squidWare.intake.presetUp();
        squidWare.scoring.anglerPosition(squidWare.scoring.anglerPos);

        while (opModeIsActive()) {

            // Quadratic controller sensitivity.
            drive  = Controls.quadratic(-gamepad1.left_stick_y);
            strafe = Controls.quadratic(gamepad1.left_stick_x);
            turn   = Controls.quadratic(gamepad1.right_stick_x);

            drivetrain.drive(drive, strafe, turn);

            if (Controls.singlePress("toggle drive", gamepad1.x)) {
                if (drivetrain instanceof MecanumFieldCentric) {
                    drivetrain = robotCentricDrive;
                } else {
                    drivetrain = fieldCentricDrive;
                }
            }
            if (Controls.singlePress("toggle reduce", gamepad1.b)) {
                if (reducedDrivePowerState == SystemState.OFF) {
                    reducedDrivePowerState = SystemState.ON;
                    fieldCentricDrive.setDriveParams(maxDriveReduced, maxStrafeReduced, maxTurnReduced, Math.PI);
                    robotCentricDrive.setDriveParams(maxDriveReduced, maxStrafeReduced, maxTurnReduced);
                } else {
                    reducedDrivePowerState = SystemState.OFF;
                    fieldCentricDrive.setDriveParams(maxDrive, maxStrafe, maxTurn, Math.PI);
                    robotCentricDrive.setDriveParams(maxDrive, maxStrafe, maxTurn);
                }
            }

            // Intake system.
            if (Controls.singlePress("intake servo", gamepad2.b)) {
                if (intakeState == UpDownState.UP) {
                    intakeState = UpDownState.DOWN;
                    squidWare.intake.presetDown();
                } else {
                    intakeState = UpDownState.UP;
                    squidWare.intake.presetUp();
                }
            } else if (Controls.spacedHold("intake servo up", gamepad2.dpad_up, 0.05)) {
                squidWare.intake.up();
            } else if (Controls.spacedHold("intake servo down", gamepad2.dpad_down, 0.05)) {
                squidWare.intake.down();
            }
            if (Controls.singlePress("intake motor", gamepad2.y)) {
                if (intakeMotorState == SystemState.OFF) {
                    intakeMotorState = SystemState.ON;
                    squidWare.intake.power(squidWare.intake.PRESET_POWER);
                } else {
                    intakeMotorState = SystemState.OFF;
                    squidWare.intake.power(0);
                }
            } else if (intakeMotorState == SystemState.OFF || Math.abs(gamepad2.right_stick_y) > 0) {
                intakeMotorState = SystemState.OFF;
                squidWare.intake.power(-gamepad2.right_stick_y);
            }

            // Internal transfer system.
            if (Controls.singlePress("conveyor motor out", gamepad2.dpad_left)) {
                if (conveyorMotorState == SystemState.OFF) {
                    conveyorMotorState = SystemState.ON;
                    squidWare.conveyor.power(-squidWare.conveyor.PRESET_POWER);
                } else {
                    conveyorMotorState = SystemState.OFF;
                    squidWare.conveyor.power(0);
                }
            } else if (Controls.singlePress("conveyor motor in", gamepad2.dpad_right)) {
                if (conveyorMotorState == SystemState.OFF) {
                    conveyorMotorState = SystemState.ON;
                    squidWare.conveyor.power(squidWare.conveyor.PRESET_POWER);
                } else {
                    conveyorMotorState = SystemState.OFF;
                    squidWare.conveyor.power(0);
                }
            }
            boolean pixelOverflowNow = false;
            if (
                conveyorMotorState == SystemState.OFF 
                || Math.abs(gamepad2.right_stick_x) > 0
                || (pixelOverflowNow = squidWare.conveyor.pixelOverflow())
            ) {
                if (pixelOverflowNow) {
                    intakeState = UpDownState.UP;
                    intakeMotorState = SystemState.OFF;
                    squidWare.intake.power(0);
                    squidWare.intake.presetUp();
                }
                conveyorMotorState = SystemState.OFF;
                squidWare.conveyor.resetState();
                squidWare.conveyor.power(gamepad2.right_stick_x);
            }

            // Scoring system.
            // Spinning servos.
            if (Controls.singlePress("spinning", gamepad2.x)) {
                if (spinningState == SystemState.ON) {
                    spinningState = SystemState.OFF;
                    squidWare.scoring.spinningOff();
                } else {
                    spinningState = SystemState.ON;
                    squidWare.scoring.spinningOn();
                }
            }
            if (Controls.singlePress("spinning panic", gamepad2.left_bumper)) {
                spinningState = SystemState.ON;
                squidWare.scoring.spinningPanic();
            }
            // Linear slide.
            squidWare.scoring.slidePower(-gamepad2.left_stick_y);
            // Angler.
            if (Controls.spacedHold("angler servo up", gamepad2.right_trigger > 0, 0.01)) {
                squidWare.scoring.anglerUp();
            } else if (Controls.spacedHold("angler servo down", gamepad2.left_trigger > 0, 0.01)) {
                squidWare.scoring.anglerDown();
            }
            // Release.
            if (gamepad1.left_bumper) {
                squidWare.scoring.releaseReset();
            } else if (gamepad1.right_bumper) {
                squidWare.scoring.releasePixel();
            }
            // Launch.
            if (Controls.singlePress("launcher", gamepad1.y)) {
                if (launchState == UpDownState.DOWN) {
                    launchState = UpDownState.UP;
                    squidWare.launcher.fire();
                } else {
                    launchState = UpDownState.DOWN;
                    squidWare.launcher.stop();
                }
            }

            // Lift.
            if (gamepad1.dpad_up) {
                squidWare.lift.power(squidWare.lift.POWER);
            } else if (gamepad1.dpad_down) {
                squidWare.lift.power(-squidWare.lift.POWER);
            } else {
                squidWare.lift.power(0);
            }

            // Combo.
            if (Controls.singlePress("g state", gamepad2.a)) {
                if (gState == SystemState.OFF) {
                    gState = SystemState.ON;
                    intakeState = UpDownState.DOWN;
                    intakeMotorState = SystemState.ON;
                    conveyorMotorState = SystemState.ON;
                    squidWare.intake.presetDown();
                    squidWare.intake.power(squidWare.intake.PRESET_POWER);
                    squidWare.conveyor.power(squidWare.conveyor.PRESET_POWER);
                } else {
                    gState = SystemState.OFF;
                    intakeState = UpDownState.UP;
                    intakeMotorState = SystemState.OFF;
                    conveyorMotorState = SystemState.OFF;
                    squidWare.intake.presetUp();
                    squidWare.intake.power(0);
                    squidWare.conveyor.power(0);
                }
            }
        }
    }

    private enum UpDownState {
        UP, DOWN
    }

    private enum SystemState {
        ON, OFF
    }
}