Gray Matter
WorkshopBLine Paths
Rough draft: nobody has reviewed this lesson yet, and it may not be how things are done this season.
LESSON 28

BLine Paths

BLine is a path library with a field editor. You draw a path in the browser, export it into the robot project, and the library turns it into a command. This lesson replaces the Leave Start timer with a drawn path.

Branchswerve-bline30 minutes
You’ll need
  • The Leave Start routine from Autonomous, and the numbers it measured.
  • coroutine.await(...) and the OpMode lifecycle, from Coroutines and Autonomous.
  • A browser for the editor at bline.run.

A path is an ordered list of elements. A waypoint sets where the robot starts, translations are the points it drives through, and rotations set the way it faces on the way. An event trigger is a marker along the path that runs a piece of code when the robot passes it.

Install the library

  1. In VS Code, open the WPILib Vendor Dependencies view. Expand INSTALL FROM URL, paste the URL below, and press Install.
  2. Build the project. Gradle downloads the library from JitPack the first time, so the first build needs internet.
BLine-Lib 2027.0.0-beta.3, WPILib 2027 alpha-7
https://raw.githubusercontent.com/edanliahovetsky/BLine-Lib/wpilib-2027/BLine-Lib-2027.json

The editor at bline.run is version 1.0.0, the one that pairs with this library. Its home page links a Windows build.

Draw a path

The editor works on the src/main/deploy/autos folder: config.json, project.json, and one file per path under paths/. It ships to the robot with every deploy.

BLine editor home screen with the Import menu open, showing Autos folder and Project archive
Home, then Import, then Autos folder. A project with no autos folder yet starts from Create project.
  1. On Home, open Import ▾, choose Autos folder, and pick src/main/deploy/autos. With no folder yet, click Create project.
  2. Choose File → New Path. Name it leave-start in the Project Navigator, press ✓, close the navigator, and pick the path in the path selector at the top.
  3. With the Waypoint tool, key 1, click the start. Type X (m) 1.0 and Y (m) 2.0 in the inspector. With the Translation tool, key 2, click once for each next point. This path needs one, at 3.5 and 2.0.
  4. With the Rotation tool, key 3, click the segment. Set Rotation (deg) to 45 and Rotation Pos (0-1) to 0.8.
  5. With the Event tool, key 4, click the segment. Set Event Pos (0-1) to 0.5 and Lib Key to atMidpoint.
  6. Press play to preview. Then choose File → Import / Export → Export Autos Folder... and put the autos folder in src/main/deploy.
A short path on the blue side of the field, with the Event Trigger inspector open: Event Pos 0.5, Lib Key atMidpoint
An event trigger selected. Lib Key is the name the robot code looks up.

Speed limits sit on the Constraints tab. Each one reads Auto and follows config.json, which ships at 2 m/s and 2 m/s². To pin one, click its chip, switch it to Manual, type the value, and press Generate.

BLine editor File menu open on Import / Export, showing Export Autos Folder
Export Autos Folder writes config.json, project.json and every path.

The builder in DriveMechanism

FollowPath.Builder is built once, in the DriveMechanism constructor, and turns every path file into a command.

The request field, and the builder in the constructor
// BLine hands back speeds relative to the robot, so the request is robot-relative too.
// Velocity asks each wheel for an exact speed, so the small corrections at the end of a path
// still turn the wheels instead of stalling against friction.
private final SwerveRequest.ApplyRobotVelocity pathRequest =
new SwerveRequest.ApplyRobotVelocity().withDriveRequestType(DriveRequestType.Velocity);
 
// Teaches BLine how to drive this robot. After this, followPath can turn any path drawn in
// the BLine editor into a command.
pathBuilder =
new FollowPath.Builder(
DriveType.SWERVE,
this, // path commands require this mechanism
() -> getPose(), // where the robot is
pose -> resetPose(pose), // used when a path says where the robot starts
() -> getRobotVelocity(), // how fast it is moving, in its own directions
velocities -> driveRobotRelative(velocities), // drive like this
// Each controller pulls the robot back toward the path. TODO: tune on your robot.
new PIDController(2.0, 0.0, 0.0), // distance to the target: m/s per meter
// At 1.0 the turn lagged and stalled short of the end tolerance in simulation.
new PIDController(3.0, 0.0, 0.0), // heading error: rad/s per radian
new PIDController(0.2, 0.0, 0.0)) // drift off the line: m/s per meter
// Paths are drawn from the blue side. On red, BLine turns them around the field center.
.withDefaultShouldFlip();

BLine reads and writes ChassisVelocities in the robot's own directions, with X as its forward, so driveRobotRelative feeds them to an ApplyRobotVelocity request. Velocity mode matters at the end of a path, where a small voltage can leave a wheel stuck against friction.

followPath(name) calls pathBuilder.build(new Path(name)), which reads deploy/autos/paths/<name>.json right there. The three gains are a starting point tuned in simulation only. If the robot stalls short of the end, raise them.

The Auto OpMode

Delete LeaveStartAuto.java. One @Autonomous class now runs every routine, and a drop-down on the dashboard picks which one.

AutoOpMode, without imports and Javadoc
@Autonomous(name = "Auto")
public class AutoOpMode extends PeriodicOpMode {
private final Selectable<Command> autoChooser = new Selectable<>();
private Command routine;
 
public AutoOpMode(Robot robot) {
// Event triggers are the markers along a path. The key in quotes must match the marker's key
// in the BLine editor exactly. This one is a stand-in that only prints a line to the driver
// station console and the log. A robot with an intake would start the intake here instead.
FollowPath.registerEventTrigger(
"atMidpoint", () -> DataLogManager.log("Leave Start: reached the midpoint"));
 
// Read the path file now, so a misspelled name fails when this OpMode is picked.
// withPoseReset puts odometry at the path's first point before it drives.
final var leaveStartPath = robot.drivetrain.followPath("leave-start").withPoseReset();
 
// The routine owns no mechanism itself. The path command it awaits claims the drivetrain for
// as long as it drives, and a second path would go on the next line.
final Command leaveStart =
Command.noRequirements(
coroutine -> {
coroutine.await(leaveStartPath);
})
.named("Leave Start");
final Command none = Command.noRequirements(coroutine -> {}).named("None");
 
autoChooser.addDefault("Leave Start", leaveStart);
autoChooser.add("None", none);
Tunables.publish("Auto", autoChooser);
}
 
@Override
public void start() {
routine = autoChooser.getSelected();
Scheduler.getDefault().schedule(routine);
}
 
@Override
public void end() {
Scheduler.getDefault().cancel(routine);
FollowPath.clearPendingEventTriggers();
}
 
@Override
public void close() {
Tunables.remove("Auto");
}
}

The event trigger is registered first, before any path command is built. The routine is a coroutine that awaits the path, so a second path is one more line under the first: coroutine.await(nextPath);.

start() reads the drop-down, because someone can still change it after picking the mode. end() cancels the routine and clears any event the path passed but has not run yet. close() takes the drop-down off the dashboard when another OpMode is picked.

A path drawn ahead of time suits a fixed route. A target chosen at runtime is a field pose, handed to Drive to Point.

Check your work

  1. Run WPILib: Simulate Robot Code and pick Auto from the autonomous list. Leave Start is already selected under Tunables/Auto.
  2. Enable and watch Drivetrain/Pose on the 2D field in AdvantageScope. Disable and enable again, then try None, then Leave Start on red.
Check

You should see

  • The pose jump to 1.0, 2.0 at enable, then the robot drive to within 3 cm of 3.5, 2.0, facing 45 degrees within 2. Positive angles turn counterclockwise.
  • Leave Start: reached the midpoint once per run in the console, a moment after the robot passes the marker.
  • A re-enable running the path again from the start, and a disable stopping the robot. None holding still, and on red the path turned around the field center.
Fails when picked
A missing file
The name in followPath does not match a file in autos/paths, autos/config.json is missing, or an empty path threw Path has no destination.
No console line
A key mismatch
Lib Key in the editor and the string in registerEventTrigger differ. They must match letter for letter, capitals included.
Wrong place
No pose reset
Without withPoseReset(), odometry keeps whatever it read before, and the robot and the path disagree about where the path begins.
BLine documentation

Check yourself

Which file does robot.drivetrain.followPath("leave-start") read?

The event never prints, but the robot drives the whole path. What is the likely cause?

Why does the path request use DriveRequestType.Velocity?

How do you make the Leave Start routine drive a second path after the first?

The robot drives the right shape but in the wrong place on the field. What is missing?

Why does AutoOpMode read the drop-down in start() and not in its constructor?

Pick an answer for each.