Mercurial v1
Commands, requirements, and a timed motor example for legacy Mercurial v1
Legacy version
Mercurial v1 is the older command-based framework. Use this guide for existing v1 projects. For the newer API, see Mercurial v2.
Overview
Mercurial v1 is a command-based framework from Dairy Foundation. Its command API models actions with lifecycle methods, requirements, and run states; command groups and a scheduler coordinate those actions. The ecosystem also includes libraries such as Core, Util, and Pasteurized, but the modules and setup depend on the release and project template.
Version and installation
This example uses dev.frozenmilk.mercurial:Mercurial:1.0.3. Keep the compatible Core, Util, and Pasteurized dependencies from your v1 project setup. The v1 documentation and Dairy project templates describe how those modules fit together.
Core concepts
- Commands describe setup, repeated execution, completion, and cleanup behavior.
- Requirements identify resources a command needs so the scheduler can handle conflicting commands.
- Run states control when a command may be scheduled, such as initialization or active .
- Command groups combine actions sequentially, in parallel, or conditionally.
- Subsystems organize the hardware and behavior owned by a .
Motor example
Configure an unloaded as testMotor. Save the selected complete class as MercurialMotorTest.java or .kt, adding your TeamCode package declaration before its imports. This example uses v1 1.0.3 and the compatible Dairy setup described above.
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
import com.qualcomm.robotcore.eventloop.opmode.OpMode;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.util.ElapsedTime;
import dev.frozenmilk.mercurial.Mercurial;
import dev.frozenmilk.mercurial.commands.Lambda;
@Autonomous(name = "Mercurial: Timed Motor Test")
@Mercurial.Attach
public class MercurialMotorTest extends OpMode {
private DcMotor motor;
private final ElapsedTime timer = new ElapsedTime();
private final Object motorRequirement = new Object();
private Lambda runMotor;
@Override
public void init() {
motor = hardwareMap.get(DcMotor.class, "testMotor");
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
runMotor = new Lambda("Run motor briefly")
.setInit(timer::reset)
.setExecute(() -> motor.setPower(0.15))
.setFinish(() -> timer.seconds() >= 1.0)
.setEnd(interrupted -> motor.setPower(0.0))
.setRequirements(motorRequirement);
}
@Override
public void start() {
runMotor.schedule();
}
@Override
public void loop() {
telemetry.addData("Elapsed seconds", "%.2f", timer.seconds());
telemetry.update();
}
@Override
public void stop() {
if (motor != null) motor.setPower(0.0);
}
}import com.qualcomm.robotcore.eventloop.opmode.Autonomous
import com.qualcomm.robotcore.eventloop.opmode.OpMode
import com.qualcomm.robotcore.hardware.DcMotor
import com.qualcomm.robotcore.util.ElapsedTime
import dev.frozenmilk.mercurial.Mercurial
import dev.frozenmilk.mercurial.commands.Lambda
@Autonomous(name = "Mercurial: Timed Motor Test")
@Mercurial.Attach
class MercurialMotorTest : OpMode() {
private lateinit var motor: DcMotor
private val timer = ElapsedTime()
private val motorRequirement = Any()
private lateinit var runMotor: Lambda
override fun init() {
motor = hardwareMap.get(DcMotor::class.java, "testMotor")
motor.zeroPowerBehavior = DcMotor.ZeroPowerBehavior.BRAKE
runMotor = Lambda("Run motor briefly")
.setInit { timer.reset() }
.setExecute { motor.power = 0.15 }
.setFinish { timer.seconds() >= 1.0 }
.setEnd { _: Boolean -> motor.power = 0.0 }
.setRequirements(motorRequirement)
}
override fun start() {
runMotor.schedule()
}
override fun loop() {
telemetry.addData("Elapsed seconds", "%.2f", timer.seconds())
telemetry.update()
}
override fun stop() {
if (::motor.isInitialized) motor.power = 0.0
}
}Hardware mapping and command construction happen in init(). Scheduling happens in start(). The command resets its timer once, requests low motor power on each execution, and finishes after one second. Its end callback removes power whether it finishes normally or is interrupted. The OpMode's stop() also writes zero.
The shared motorRequirement identifies the resource. Reuse that same object in other commands controlling this motor so requirements can prevent conflicts. Do not create a fresh marker for each command and expect them to exclude one another.
Test , START, command completion, and STOP on the robot before using the command in a larger routine.
Team fit
A command framework can make shared behaviors and sequences easier to compose, but it adds concepts and dependencies. Compare the framework with your team's experience, the current project template, and the level of support available before adopting it. A small robot can also be programmed directly with FTC SDK OpModes.