Skip to content
CrocoDocs
Starter GuideProgrammingEngineeringDesignPortfolio
Documentation

Introduction

IntroductionBIOBUZZ game overview

Getting started

Getting startedBlocksOnBot JavaAndroid StudioYour first OpModeJava basicsMotors and servosNonblocking codeDebugging robot code

Control systems

Control systemsJoystick mappingField-centric mecanum drivePID controlFeedforwardFlywheel velocity controlSystem identificationMotion profilingKalman filterLow-pass filter

Autonomous

AutonomousTimed and encoder movementOdometryLocalization troubleshootingMotion planningReliable autonomous sequencesPure PursuitSensor fusionAuto to TeleOp handoff

Season topics

Season topicsProjectile mathematicsTurret aimingShooting on the move

Code practices

Code practicesNaming conventionsCode organizationLoop timing and bulk readsFormattingComments and documentationTeam collaboration

Libraries

LibrariesNextFTCFTCLibSolversLibPedro PathingRoad RunnerFTC DashboardMercurial v2Mercurial v1PanelsSloth

Community software

Community softwareBlaze FTCOpenLoopHive Vision

Sensors and vision

Vision overviewVision basicsLimelight 3A
Starter Guide
Engineering
Design
Portfolio
Appearance

Mercurial v2

Programs, sequences, and concurrent robot tasks with Mercurial v2

Overview

Mercurial v2 runs tasks as lightweight processes called fibers. A can follow a path while an watches a , with messages coordinating what each does. and Kotlin use the same underlying library, with different syntax for building programs.

Beta release

This guide uses MercurialFTC 2.0.1-beta1. APIs may change between beta releases. Projects using the older dev.frozenmilk.mercurial packages should use the Mercurial v1 guide.

Setup

The upstream example projects use SDK 12 and Dairy's TeamCode Gradle plugin. Start with a compatible Dairy project setup, then add the release repository and FTC bindings. In a Groovy Gradle project, the dependency is:

repositories {
    maven {
        name = "dairyReleases"
        url = uri("https://repo.dairy.foundation/releases")
    }
}

dependencies {
    implementation("dev.frozenmilk.dairy:MercurialFTC:2.0.1-beta1")
}

Put the repository in your project's dependency repository configuration and the dependency in the TeamCode module. Gradle projects with centralized repositories keep that first block in settings.gradle instead.

MercurialFTC brings in Mercurial core, Sloth, and Util. Kotlin projects also need Kotlin enabled in their Gradle setup; the Dairy examples use ftc { kotlin() }. Keep the plugin and Kotlin versions compatible with that setup, sync Gradle, and do a full build and install after adding dependencies.

Programs

MercurialFTC.teleop and MercurialFTC.autonomous create programs that appear in the 's list. Store a program in a public static field in Java, or a public top-level property in Kotlin, so Mercurial can discover it. .withName(...) sets its displayed name.

Each program receives a Context containing the hardware map, gamepads, telemetry, scheduler, and current OpMode state. waitForStart() runs the scheduler during until START or STOP. After it returns, check inLoop before starting motion: STOP during INIT also ends the wait.

dropToScheduler() keeps polling the scheduler until STOP. The program returns null when it has no follow-up program. Mercurial initializes its thread context automatically for these programs.

Sequences and fibers

A builder describes work; constructing one does not run it. These are the main pieces of the example below:

APIPurpose
execRun a short piece of ordinary Java or Kotlin code.
sequenceRun builders in order.
waitFor with secondsWait for a duration while allowing other fibers to run.
spawnable()Compile a builder into a reusable program that can be spawned.
spawnLink()Create and schedule a fiber linked to the current fiber.

Keep exec callbacks short. A blocking Thread.sleep() or long while loop inside one holds up other work on the standard scheduler. Use Mercurial's waits and loop builders when a task needs to pause or repeat.

Motor example

Configure an unloaded motor as testMotor and secure it for a bench test. Choose one language and save the file under the matching TeamCode package. These examples use org.firstinspires.ftc.teamcode; change the package if your project uses another one.

The motor stays still during INIT, runs at 0.15 power for about one second after START, then stops. The OpMode remains active until you press STOP.

Save as MercurialV2MotorTest.java.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.hardware.DcMotor;
import dev.frozenmilk.dairy.mercurial.ftc.MercurialFTC;
import dev.frozenmilk.dairy.mercurial.ftc.MercurialFTC.RegisterableProgram;

import static dev.frozenmilk.dairy.mercurial.continuations.Continuations.exec;
import static dev.frozenmilk.dairy.mercurial.continuations.Continuations.seconds;
import static dev.frozenmilk.dairy.mercurial.continuations.Continuations.sequence;
import static dev.frozenmilk.dairy.mercurial.continuations.Continuations.waitFor;

public class MercurialV2MotorTest {
    public static final RegisterableProgram motorTest =
        MercurialFTC.autonomous(ctx -> {
            DcMotor motor = ctx.hardwareMap().get(DcMotor.class, "testMotor");

            try {
                motor.setPower(0.0);
                motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
                motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
                ctx.waitForStart();

                if (ctx.inLoop()) {
                    sequence(
                        exec(() -> motor.setPower(0.15)),
                        waitFor.bind(seconds(1.0)),
                        exec(() -> motor.setPower(0.0))
                    ).spawnable().spawnLink();

                    ctx.dropToScheduler();
                }

                return null;
            } finally {
                motor.setPower(0.0);
            }
        }).withName("Mercurial v2: Timed Motor Test");
}

Save as MercurialV2MotorTest.kt.

package org.firstinspires.ftc.teamcode

import com.qualcomm.robotcore.hardware.DcMotor
import dev.frozenmilk.dairy.mercurial.continuations.Continuations.exec
import dev.frozenmilk.dairy.mercurial.continuations.Continuations.seconds
import dev.frozenmilk.dairy.mercurial.continuations.Continuations.sequence
import dev.frozenmilk.dairy.mercurial.continuations.Continuations.waitFor
import dev.frozenmilk.dairy.mercurial.ftc.MercurialFTC

val motorTest = MercurialFTC.autonomous {
    val motor = hardwareMap.get(DcMotor::class.java, "testMotor")

    try {
        motor.power = 0.0
        motor.mode = DcMotor.RunMode.RUN_WITHOUT_ENCODER
        motor.zeroPowerBehavior = DcMotor.ZeroPowerBehavior.BRAKE
        waitForStart()

        if (inLoop) {
            sequence(
                exec { motor.power = 0.15 },
                waitFor * seconds(1.0),
                exec { motor.power = 0.0 },
            ).spawnable().spawnLink()

            dropToScheduler()
        }

        null
    } finally {
        motor.power = 0.0
    }
}.withName("Mercurial v2: Timed Motor Test")

The Java .bind(...) and Kotlin * forms supply the wait's duration. seconds(1.0) makes a time supplier that is evaluated when the wait runs, so the second starts after the motor-on step.

The final exec stops the motor when the sequence finishes. The outer finally also removes power when the program exits early. Keep both: a fiber interrupted during the wait can skip the remaining sequence steps.

Test STOP during INIT, STOP during the timed motion, and normal completion. Confirm zero motor power in each case before adapting this to a loaded mechanism.

Concurrent tasks

Spawn separate fibers for tasks that need to progress independently. A second motor sequence can run alongside the first because each wait yields to the scheduler. Links propagate abnormal exits between fibers, which helps related tasks fail together; a normal completion does not interrupt its linked peers.

For a group of builders, parallel(...) waits for every member to finish. race(...) finishes when one member exits and cancels the rest. deadline(...) uses one designated task to decide when the other tasks should stop. These helpers are useful when an intake or lift must run alongside a path.

For longer-lived mechanism control, channels carry messages between fibers. A mechanism can own its hardware and accept requests through a channel. EventManager publishes events to subscribers, while StateMachine and SwitchedStateMachine help organize modes such as idle, intaking, and scoring.

Agree on which fiber controls each motor. Running two fibers does not automatically resolve competing power commands. V2's command builder has no v1-style requirements system; mechanism ownership and message handling need to be designed into the program.

Moving from v1

V1's Lambda, @Mercurial.Attach, and command requirements are different APIs. A migration involves changing how the robot's tasks are organized, rather than just updating the dependency coordinate.

Move one behavior at a time. Begin with a sequence like the motor test, add a concurrent task, then introduce channels or state machines where mechanisms need to coordinate. MercurialFTC also has a Pedro , but path following still needs your robot's follower configuration and localization setup.

Sources

  • MercurialFTC installation and published dependency metadata
  • Mercurial v2 introduction and fiber model
  • Java sequential programming and Kotlin sequential programming
  • Java program registration, Kotlin program registration, and Context lifecycle
  • Concurrent programming, event managers, and state machines
  • MercurialFTC 2.0.1-beta1 release and Dairy Gradle plugins

FTC Dashboard

Official FTC Dashboard features, setup, and version notes

Mercurial v1

Commands, requirements, and a timed motor example for legacy Mercurial v1

On this page

OverviewSetupProgramsSequences and fibersMotor exampleConcurrent tasksMoving from v1Sources