CrocoDocs
CrocoDocs

Introduction

What is CrocoDocs?Season Breakdown

Getting Started

Programming in FTCJavaBlocksAndroid Studio

Control Systems

IntroductionJoystick MappingPID ControlMotion ProfilingKalman FilterLow-Pass Filter

Autonomous

IntroductionTime vs Encoder-Based MovementOdometryMotion PlanningPure PursuitSensor Fusion

Codebase Etiquette and Good Practices

IntroductionNaming ConventionsCode OrganizationComments and DocumentationTeam Collaboration

Libraries

LibrariesNextFTCPedro PathingFTC DashboardMercurialPanelsSloth

Sensors and Vision

Vision OverviewVision Basics

Pedro Pathing

Modern path following system with smooth trajectories

What is Pedro Pathing?

Pedro Pathing is a modern path following library for FTC robots. It's designed to be easier to use than Road Runner while still providing smooth, accurate autonomous movements with Bézier curves and pure pursuit path following.

Named after Pedro, it focuses on simplicity and performance — getting your robot moving autonomously with minimal setup.

Why Use Pedro Pathing?

Easier Than Road Runner:

  • Simpler setup process
  • Less tuning required
  • Faster to get working
  • Great for new teams

Smooth Paths:

  • Follows curved trajectories naturally
  • Avoids jerky movements
  • Looks professional during matches
  • Better for game piece manipulation

Modern Features:

  • Real-time path visualization
  • Support for holonomic and tank drives
  • Built-in localization
  • Works with IMU and odometry pods

Installation

Step 1: Add JitPack Repository

In your build.gradle (Project level):

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

Step 2: Add Dependency

In your build.gradle (Module: TeamCode):

dependencies {
    implementation 'com.github.pedro-path:pedro-path:1.0.0'
    // Check GitHub for latest version
}

Step 3: Sync Project

Click "Sync Now" in Android Studio.

Quick Start Guide

1. Set Up Your Robot Configuration

Create a robot configuration with your motors and sensors:

import org.pedropathing.follower.Follower;
import org.pedropathing.localization.Pose;
import org.pedropathing.pathgen.PathBuilder;
import org.pedropathing.pathgen.Path;

@Autonomous(name = "Pedro Example")
public class PedroAuto extends LinearOpMode {
    private Follower follower;
    
    @Override
    public void runOpMode() {
        // Initialize follower with your drive motors
        follower = new Follower(hardwareMap);
        
        // Set starting position (x, y, heading in radians)
        follower.setStartingPose(new Pose(0, 0, 0));
        
        waitForStart();
        
        // Follow paths here
    }
}
import org.pedropathing.follower.Follower
import org.pedropathing.localization.Pose
import org.pedropathing.pathgen.PathBuilder
import org.pedropathing.pathgen.Path

@Autonomous(name = "Pedro Example")
class PedroAuto : LinearOpMode() {
    private lateinit var follower: Follower
    
    override fun runOpMode() {
        // Initialize follower with your drive motors
        follower = Follower(hardwareMap)
        
        // Set starting position (x, y, heading in radians)
        follower.setStartingPose(Pose(0.0, 0.0, 0.0))
        
        waitForStart()
        
        // Follow paths here
    }
}

2. Create a Simple Path

Build a path using the PathBuilder:

// Create a path to drive forward 40 inches
Path path = new PathBuilder()
    .addLine(new Pose(0, 0, 0), new Pose(40, 0, 0))
    .build();

// Follow the path
follower.followPath(path);

// Wait until path is complete
while (opModeIsActive() && follower.isBusy()) {
    follower.update();
    telemetry.addData("X", follower.getPose().getX());
    telemetry.addData("Y", follower.getPose().getY());
    telemetry.update();
}
// Create a path to drive forward 40 inches
val path = PathBuilder()
    .addLine(Pose(0.0, 0.0, 0.0), Pose(40.0, 0.0, 0.0))
    .build()

// Follow the path
follower.followPath(path)

// Wait until path is complete
while (opModeIsActive() && follower.isBusy()) {
    follower.update()
    telemetry.addData("X", follower.pose.x)
    telemetry.addData("Y", follower.pose.y)
    telemetry.update()
}

Path Building

Straight Lines

Path straightPath = new PathBuilder()
    .addLine(new Pose(0, 0, 0), new Pose(40, 0, 0))  // Drive forward
    .addLine(new Pose(40, 0, 0), new Pose(40, 30, 0))  // Strafe right
    .build();
val straightPath = PathBuilder()
    .addLine(Pose(0.0, 0.0, 0.0), Pose(40.0, 0.0, 0.0))  // Drive forward
    .addLine(Pose(40.0, 0.0, 0.0), Pose(40.0, 30.0, 0.0))  // Strafe right
    .build()

Curved Paths (Bézier Curves)

Path curvedPath = new PathBuilder()
    .addBezierCurve(
        new Pose(0, 0, 0),           // Start point
        new Pose(20, 20, 0),         // Control point 1
        new Pose(40, 20, 0),         // Control point 2
        new Pose(60, 0, 0)           // End point
    )
    .build();
val curvedPath = PathBuilder()
    .addBezierCurve(
        Pose(0.0, 0.0, 0.0),           // Start point
        Pose(20.0, 20.0, 0.0),         // Control point 1
        Pose(40.0, 20.0, 0.0),         // Control point 2
        Pose(60.0, 0.0, 0.0)           // End point
    )
    .build()

Sequential Paths

// Create multiple paths for a full autonomous routine
Path path1 = new PathBuilder()
    .addLine(new Pose(0, 0, 0), new Pose(40, 0, 0))
    .build();

Path path2 = new PathBuilder()
    .addLine(new Pose(40, 0, 0), new Pose(40, 30, 0))
    .build();

follower.followPath(path1);
while (opModeIsActive() && follower.isBusy()) {
    follower.update();
}

follower.followPath(path2);
while (opModeIsActive() && follower.isBusy()) {
    follower.update();
}
// Create multiple paths for a full autonomous routine
val path1 = PathBuilder()
    .addLine(Pose(0.0, 0.0, 0.0), Pose(40.0, 0.0, 0.0))
    .build()

val path2 = PathBuilder()
    .addLine(Pose(40.0, 0.0, 0.0), Pose(40.0, 30.0, 0.0))
    .build()

follower.followPath(path1)
while (opModeIsActive() && follower.isBusy()) {
    follower.update()
}

follower.followPath(path2)
while (opModeIsActive() && follower.isBusy()) {
    follower.update()
}

Paths with Heading Control

// Turn while moving
Path turnPath = new PathBuilder()
    .addLine(
        new Pose(0, 0, 0),              // Start facing forward
        new Pose(40, 0, Math.PI / 2)    // End facing right (90°)
    )
    .build();
// Turn while moving
val turnPath = PathBuilder()
    .addLine(
        Pose(0.0, 0.0, 0.0),              // Start facing forward
        Pose(40.0, 0.0, Math.PI / 2)      // End facing right (90°)
    )
    .build()

Complete Autonomous Example

@Autonomous(name = "Pedro Full Auto")
public class PedroFullAuto extends LinearOpMode {
    private Follower follower;
    private DcMotor armMotor;
    private Servo claw;
    
    @Override
    public void runOpMode() {
        // Initialize Pedro Pathing
        follower = new Follower(hardwareMap);
        follower.setStartingPose(new Pose(-36, -60, Math.toRadians(90)));
        
        // Initialize other hardware
        armMotor = hardwareMap.get(DcMotor.class, "arm");
        claw = hardwareMap.get(Servo.class, "claw");
        
        // Build paths
        Path toBasket = new PathBuilder()
            .addBezierCurve(
                new Pose(-36, -60, Math.toRadians(90)),
                new Pose(-24, -48, Math.toRadians(45)),
                new Pose(-12, -12, Math.toRadians(45))
            )
            .build();
        
        Path toSamples = new PathBuilder()
            .addLine(
                new Pose(-12, -12, Math.toRadians(45)),
                new Pose(-48, -48, Math.toRadians(0))
            )
            .build();
        
        waitForStart();
        
        if (opModeIsActive()) {
            // Score pre-load
            raiseArm();
            follower.followPath(toBasket);
            waitForPath();
            
            releaseGamePiece();
            lowerArm();
            
            // Get samples
            follower.followPath(toSamples);
            waitForPath();
            
            grabGamePiece();
            
            // Return to basket
            follower.followPath(toBasket);
            waitForPath();
            
            releaseGamePiece();
        }
    }
    
    private void waitForPath() {
        while (opModeIsActive() && follower.isBusy()) {
            follower.update();
            
            // Show position for debugging
            telemetry.addData("X", follower.getPose().getX());
            telemetry.addData("Y", follower.getPose().getY());
            telemetry.addData("Heading", Math.toDegrees(follower.getPose().getHeading()));
            telemetry.update();
        }
    }
    
    private void raiseArm() {
        armMotor.setTargetPosition(1000);
        armMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        armMotor.setPower(0.8);
        sleep(500);
    }
    
    private void lowerArm() {
        armMotor.setTargetPosition(0);
        armMotor.setMode(DcMotor.RunMode.RUN_TO_POSITION);
        armMotor.setPower(0.8);
        sleep(500);
    }
    
    private void grabGamePiece() {
        claw.setPosition(1.0);
        sleep(300);
    }
    
    private void releaseGamePiece() {
        claw.setPosition(0.0);
        sleep(300);
    }
}
@Autonomous(name = "Pedro Full Auto")
class PedroFullAuto : LinearOpMode() {
    private lateinit var follower: Follower
    private lateinit var armMotor: DcMotor
    private lateinit var claw: Servo
    
    override fun runOpMode() {
        // Initialize Pedro Pathing
        follower = Follower(hardwareMap)
        follower.setStartingPose(Pose(-36.0, -60.0, Math.toRadians(90.0)))
        
        // Initialize other hardware
        armMotor = hardwareMap.get(DcMotor::class.java, "arm")
        claw = hardwareMap.get(Servo::class.java, "claw")
        
        // Build paths
        val toBasket = PathBuilder()
            .addBezierCurve(
                Pose(-36.0, -60.0, Math.toRadians(90.0)),
                Pose(-24.0, -48.0, Math.toRadians(45.0)),
                Pose(-12.0, -12.0, Math.toRadians(45.0))
            )
            .build()
        
        val toSamples = PathBuilder()
            .addLine(
                Pose(-12.0, -12.0, Math.toRadians(45.0)),
                Pose(-48.0, -48.0, Math.toRadians(0.0))
            )
            .build()
        
        waitForStart()
        
        if (opModeIsActive()) {
            // Score pre-load
            raiseArm()
            follower.followPath(toBasket)
            waitForPath()
            
            releaseGamePiece()
            lowerArm()
            
            // Get samples
            follower.followPath(toSamples)
            waitForPath()
            
            grabGamePiece()
            
            // Return to basket
            follower.followPath(toBasket)
            waitForPath()
            
            releaseGamePiece()
        }
    }
    
    private fun waitForPath() {
        while (opModeIsActive() && follower.isBusy()) {
            follower.update()
            
            // Show position for debugging
            telemetry.addData("X", follower.pose.x)
            telemetry.addData("Y", follower.pose.y)
            telemetry.addData("Heading", Math.toDegrees(follower.pose.heading))
            telemetry.update()
        }
    }
    
    private fun raiseArm() {
        armMotor.targetPosition = 1000
        armMotor.mode = DcMotor.RunMode.RUN_TO_POSITION
        armMotor.power = 0.8
        sleep(500)
    }
    
    private fun lowerArm() {
        armMotor.targetPosition = 0
        armMotor.mode = DcMotor.RunMode.RUN_TO_POSITION
        armMotor.power = 0.8
        sleep(500)
    }
    
    private fun grabGamePiece() {
        claw.position = 1.0
        sleep(300)
    }
    
    private fun releaseGamePiece() {
        claw.position = 0.0
        sleep(300)
    }
}

Tuning Your Robot

Pedro Pathing requires minimal tuning compared to Road Runner:

1. Set Drive Constants

// In your Follower initialization
follower.setMaxPower(0.8);              // Max drive power (0-1)
follower.setTranslationalCorrection(0.1); // How aggressively to correct position errors
follower.setHeadingCorrection(0.1);      // How aggressively to correct heading errors
// In your Follower initialization
follower.setMaxPower(0.8)              // Max drive power (0-1)
follower.setTranslationalCorrection(0.1) // How aggressively to correct position errors
follower.setHeadingCorrection(0.1)      // How aggressively to correct heading errors

2. Test and Adjust

  • Run simple paths and observe behavior
  • Increase correction if robot is off-target
  • Decrease correction if robot oscillates
  • Adjust max power for speed vs. accuracy

Localization

Pedro Pathing supports multiple localization methods:

IMU Only (Simpler)

// Uses drive encoders + IMU for heading
follower.setLocalizationMode(Follower.LocalizationMode.IMU);
// Uses drive encoders + IMU for heading
follower.setLocalizationMode(Follower.LocalizationMode.IMU)

Three-Wheel Odometry (More Accurate)

// Requires odometry pods
follower.setLocalizationMode(Follower.LocalizationMode.THREE_WHEEL);
// Requires odometry pods
follower.setLocalizationMode(Follower.LocalizationMode.THREE_WHEEL)

Pros and Cons

Advantages:

  • ✅ Much easier to set up than Road Runner
  • ✅ Less tuning required
  • ✅ Smooth curved paths
  • ✅ Good for beginners
  • ✅ Active development and support
  • ✅ Works well for most FTC needs

Disadvantages:

  • ❌ Newer library (less battle-tested)
  • ❌ Smaller community than Road Runner
  • ❌ May not be as optimized for very high speeds
  • ❌ Less advanced features than Road Runner

When to Use Pedro Pathing

Great for:

  • First-year teams learning autonomous
  • Teams that struggled with Road Runner
  • Robots that don't need extreme speed
  • Quick autonomous development

Consider Road Runner if:

  • You need maximum speed optimization
  • Your team already knows Road Runner
  • You want the most mature solution

Tips for Success

  1. Start Simple: Test with straight lines before curves
  2. Visualize: Use telemetry to confirm positions
  3. Iterate: Build paths incrementally
  4. Document: Save working coordinates for future seasons
  5. Combine: Use Pedro for driving, PID for mechanisms

Resources

  • GitHub: Pedro Pathing Repository
  • Documentation: Check the GitHub wiki
  • Examples: Sample OpModes in the repository
  • Discord: FTC Discord communities

Next Steps

  1. Install Pedro Pathing in your project
  2. Create a simple path following OpMode
  3. Test basic movements (forward, strafe, turn)
  4. Build a complete autonomous routine
  5. Tune parameters for your robot
  6. Integrate with your existing code!

NextFTC

Command-based framework with built-in control systems and gamepad bindings

FTC Dashboard

Real-time telemetry, configuration, and debugging tool

On this page

What is Pedro Pathing?Why Use Pedro Pathing?InstallationStep 1: Add JitPack RepositoryStep 2: Add DependencyStep 3: Sync ProjectQuick Start Guide1. Set Up Your Robot Configuration2. Create a Simple PathPath BuildingStraight LinesCurved Paths (Bézier Curves)Sequential PathsPaths with Heading ControlComplete Autonomous ExampleTuning Your Robot1. Set Drive Constants2. Test and AdjustLocalizationIMU Only (Simpler)Three-Wheel Odometry (More Accurate)Pros and ConsWhen to Use Pedro PathingTips for SuccessResourcesNext Steps