Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
*
* <p>The two gamepads passed in are the same objects on every tick, so a button stays held until the script
* releases it, exactly like a real one. Write only fields ({@code gamepad1.left_stick_y = -1f},
* {@code gamepad1.a = true}); do not keep references to them outside the call.
* {@code gamepad1.a = true}); do not keep references to them outside the call. A button can be pressed by either
* of its names ({@code a} or {@code cross}, {@code y} or {@code triangle}, {@code back} or {@code share}, ...): the
* OpMode sees both names pressed, as it would on the robot.
*
* <p>A script must be a pure function of its arguments (and of state it owns) for a run to be deterministic.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ final class OpModeContext {
/** The operator's gamepad as the Driver Station holds it. */
final Gamepad driverStationGamepad2 = new Gamepad();

/** Where a script's gamepad is staged on its way to the OpMode, with its PlayStation names folded in. */
private final Gamepad staged = new Gamepad();

/** Sim time, in seconds, at which {@code getRuntime()} reads zero. */
private double runtimeOriginSeconds;

Expand Down Expand Up @@ -73,6 +76,26 @@ TelemetryRewiring inject(HardwareMap hardwareMap, RecordingTelemetry telemetry,
return rewiring;
}

/**
* Copies a script's gamepad into the OpMode's the way the SDK delivers Driver Station data, with the buttons
* the script pressed by their PlayStation names counted too. {@code Gamepad.copy} carries only the Xbox names
* ({@code toByteArray}) and sets the PlayStation ones from them ({@code updateButtonAliases}), so on its own it
* drops {@code triangle = true}: a real Driver Station has one button there, sent under its Xbox name. Each
* PlayStation name is ORed into its Xbox twin on a staging copy, never on the script's gamepad, so a button
* the script releases is released. The pairs are the SDK's ({@code updateButtonAliases}, RobotCore 12.0.0).
*/
private void deliver(Gamepad script, Gamepad toOpMode) {
staged.copy(script);
staged.a |= script.cross;
staged.b |= script.circle;
staged.x |= script.square;
staged.y |= script.triangle;
staged.back |= script.share;
staged.start |= script.options;
staged.guide |= script.ps;
toOpMode.copy(staged);
}

/** Makes {@code getRuntime()} and {@code time} count from the sim clock's current reading: INIT. */
void startRuntimeHere() {
runtimeOriginSeconds = timer.time();
Expand All @@ -96,8 +119,8 @@ void markStopRequested() {
* SDK's edge detection), and {@code time} and {@code getRuntime()} are set to the sim runtime.
*/
void beforeUserCode() {
opMode.gamepad1.copy(driverStationGamepad1);
opMode.gamepad2.copy(driverStationGamepad2);
deliver(driverStationGamepad1, opMode.gamepad1);
deliver(driverStationGamepad2, opMode.gamepad2);
double runtimeSeconds = timer.time() - runtimeOriginSeconds;
opMode.time = runtimeSeconds;
opMode.resetRuntime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,48 @@ void theScriptDrivesTheGamepadsTickByTickAndEdgeDetectionSeesOnePressPerPress()
assertEquals(2, opMode.presses, "aWasPressed() fires once per press, not once per tick held");
}

/** Records each tick what the OpMode sees of one button, under both its names. */
static class TriangleReader extends OpMode {
final List<String> seen = new ArrayList<>();
int trianglePresses, yPresses, sharePresses;

@Override public void init() { }

@Override
public void loop() {
seen.add((gamepad1.triangle ? "T" : "-") + (gamepad1.y ? "Y" : "-") + (gamepad1.back ? "B" : "-")
+ (gamepad1.share ? "S" : "-"));
if (gamepad1.triangleWasPressed()) {
trianglePresses++;
}
if (gamepad1.yWasPressed()) {
yPresses++;
}
if (gamepad1.shareWasPressed()) {
sharePresses++;
}
}
}

@Test
void aButtonPressedByItsPlayStationNameReachesTheOpModeUnderBothNamesAndReleases() {
TriangleReader opMode = new TriangleReader();
GamepadScript script = (phase, tick, gamepad1, gamepad2) -> {
if (phase != OpModePhase.ACTIVE) {
return;
}
gamepad1.triangle = tick >= 1 && tick < 3; // pressed by its PlayStation name for two ticks
gamepad1.share = tick == 5; // and share tapped once
};
runner(opMode, new FakeTimer()).activeTicks(7).gamepads(script).run();

assertEquals(Arrays.asList("----", "TY--", "TY--", "----", "----", "--BS", "----"), opMode.seen,
"triangle and y are one button, and so are share and back; released when the script lets go");
assertEquals(1, opMode.trianglePresses, "triangleWasPressed() once");
assertEquals(1, opMode.yPresses, "yWasPressed() once: it is the same button");
assertEquals(1, opMode.sharePresses, "shareWasPressed() once");
}

@Test
void requestOpModeStopRunsStopAtOnceAndNothingAfterIt() {
FakeTimer timer = new FakeTimer();
Expand Down