diff --git a/src/main/java/com/team766/framework/OIFragment.java b/src/main/java/com/team766/framework/OIFragment.java index 20336a7ff..d5c61bdf8 100644 --- a/src/main/java/com/team766/framework/OIFragment.java +++ b/src/main/java/com/team766/framework/OIFragment.java @@ -1,28 +1,30 @@ package com.team766.framework; +import com.team766.logging.Category; +import com.team766.logging.Severity; import java.util.LinkedList; import java.util.List; import java.util.function.BooleanSupplier; /** - * Fragment of an OI, with facilities to make it easy to set up {@link OICondition}s for usage in the fragment's + * Fragment of an OI, with facilities to make it easy to set up {@link Condition}s for usage in the fragment's * {@link #handleOI} method. * * The overall OI for a robot will contain a set of fragments, typically one per set of controls (eg, driver, boxop, debug), - * and it will call {@link #runOI(Context)} once per its own loop. During each call to {@link #runOI}, the fragment - * will evaluate any {@link OICondition}s that were created for this fragment. This simplifies OI logic that checks if a + * and it will call {@link #run(Context)} once per its own loop. During each call to {@link #runOI}, the fragment + * will evaluate any {@link Condition}s that were created for this fragment. This simplifies OI logic that checks if a * specific condition is currently triggering (eg pressing or holding down a joystick button) or if a condition that had been triggering * in a previous iteration of the OI loop is no longer triggering in this iteration. */ public abstract class OIFragment extends LoggingBase { - protected class OICondition { + protected class Condition { private final BooleanSupplier condition; private boolean triggering = false; private boolean newlyTriggering = false; private boolean finishedTriggering = false; - public OICondition(BooleanSupplier condition) { + public Condition(BooleanSupplier condition) { this.condition = condition; register(this); } @@ -53,13 +55,15 @@ public boolean isFinishedTriggering() { } private final String name; - private final List conditions = new LinkedList(); + private final List conditions = new LinkedList(); + private boolean conditionsEvaluated = false; /** * Creates a new OIFragment. * @param name The name of this part of the OI (eg, "BoxOpOI"). Used for logging. */ public OIFragment(String name) { + loggerCategory = Category.OPERATOR_INTERFACE; this.name = name; } @@ -67,6 +71,7 @@ public OIFragment(String name) { * Creates a new OIFragment, using the name of the sub-class. */ public OIFragment() { + loggerCategory = Category.OPERATOR_INTERFACE; this.name = this.getClass().getSimpleName(); } @@ -74,43 +79,52 @@ public final String getName() { return name; } - private void register(OICondition condition) { + private void register(Condition condition) { conditions.add(condition); } - /** - * Called at the beginning of {@link #runOI(Context)}, before evaluating any of the registered conditions - * and before calling {@link #handleOI(Context)}. Subclasses should override this if needed. - */ - protected void handlePre() {} - /** * OIFragments must override this method to implement their OI logic. Typically called via the overall - * OI's loop, once per iteration through the loop. Can use any {@link OICondition}s - * they have set up to simplify checking if the {@link OICondition} is {@link OICondition#isTriggering()}, + * OI's loop, once per iteration through the loop. Can use any {@link Condition}s + * they have set up to simplify checking if the {@link Condition} is {@link Condition#isTriggering()}, * or, if it had been triggering in a previous iteration of the loop, if it is now - * {@link OICondition#isFinishedTriggering()}. + * {@link Condition#isFinishedTriggering()}. + * + * When implementing handleOI, you must call {@link #evaluateConditions()} in order to use any + * Conditions; omitting this call will result in calls to {@link #run} throwing an IllegalStateException. * * @param context The {@link Context} running the OI. */ protected abstract void handleOI(Context context); /** - * Called after {@link #handleOI}, at the end of {@link #runOI}. Subclasses should override this if needed. + * Subclasses should call this once per call to {@link #handleOI}. Evaluates each of the + * conditions for this fragment. */ - protected void handlePost() {} + protected void evaluateConditions() { + if (conditionsEvaluated) { + log(Severity.WARNING, "evaluateConditions() called multiple times in this loop!"); + throw new IllegalStateException( + "evaluateConditions() called multiple times in this loop!"); + } + for (Condition condition : conditions) { + condition.evaluate(); + } + conditionsEvaluated = true; + } /** * Called by a Robot's OI class, once per its loop. - * Calls {@link #handlePre()}, evaluates all conditions once per call, and calls {@link #handlePost()}. * @param context The {@link Context} running the OI. */ - public final void runOI(Context context) { - handlePre(); - for (OICondition condition : conditions) { - condition.evaluate(); - } + public final void run(Context context) { + // reset for the next call + conditionsEvaluated = false; + handleOI(context); - handlePost(); + if (conditions.size() > 0 && !conditionsEvaluated) { + log(Severity.WARNING, "Fragment did not call evaluateCondition()!"); + throw new IllegalStateException("Fragment did not call evaluateCondition!"); + } } } diff --git a/src/main/java/com/team766/robot/common/DriverOI.java b/src/main/java/com/team766/robot/common/DriverOI.java index 4b2ff58f2..dbd0b7f36 100644 --- a/src/main/java/com/team766/robot/common/DriverOI.java +++ b/src/main/java/com/team766/robot/common/DriverOI.java @@ -19,7 +19,7 @@ public class DriverOI extends OIFragment { protected double leftJoystickY = 0; protected boolean isCross = false; - private final OICondition movingJoysticks; + private final Condition movingJoysticks; public DriverOI(Drive drive, JoystickReader leftJoystick, JoystickReader rightJoystick) { this.drive = drive; @@ -27,7 +27,7 @@ public DriverOI(Drive drive, JoystickReader leftJoystick, JoystickReader rightJo this.rightJoystick = rightJoystick; movingJoysticks = - new OICondition( + new Condition( () -> !isCross && Math.abs(leftJoystickX) @@ -51,6 +51,8 @@ public void handleOI(Context context) { -createJoystickDeadzone(rightJoystick.getAxis(InputConstants.AXIS_LEFT_RIGHT)) * ControlConstants.MAX_ROTATIONAL_VELOCITY; // For steer + evaluateConditions(); + if (leftJoystick.getButtonPressed(InputConstants.BUTTON_RESET_GYRO)) { drive.resetGyro(); } diff --git a/src/main/java/com/team766/robot/example/DriverOI.java b/src/main/java/com/team766/robot/example/DriverOI.java new file mode 100644 index 000000000..50cf4973a --- /dev/null +++ b/src/main/java/com/team766/robot/example/DriverOI.java @@ -0,0 +1,44 @@ +package com.team766.robot.example; + +import com.team766.framework.Context; +import com.team766.framework.OIFragment; +import com.team766.hal.JoystickReader; +import com.team766.robot.example.constants.InputConstants; +import com.team766.robot.example.procedures.*; + +public class DriverOI extends OIFragment { + private final JoystickReader joystick; + private double joystickX; + private double joystickY; + + // add any conditions (joystick inputs, etc) + private Condition button1; + private Condition moveJoystick; + + // add any mechanisms to the constructor arguments as well + public DriverOI(JoystickReader joystick) { + super("DriverOI"); + this.joystick = joystick; + + button1 = new Condition(() -> joystick.getButton(InputConstants.BUTTON_TRIGGER)); + moveJoystick = new Condition(() -> Math.abs(joystickX) > 0 || Math.abs(joystickY) > 0); + } + + @Override + protected void handleOI(Context context) { + joystickX = joystick.getAxis(InputConstants.AXIS_X); + joystickY = joystick.getAxis(InputConstants.AXIS_Y); + + evaluateConditions(); + + if (button1.isNewlyTriggering()) { + // handle button press + } else if (button1.isFinishedTriggering()) { + // handle button release + } + + if (moveJoystick.isTriggering()) { + // handle joystick movement + } + } +} diff --git a/src/main/java/com/team766/robot/example/OI.java b/src/main/java/com/team766/robot/example/OI.java index ba3a45ee4..97c0fd364 100644 --- a/src/main/java/com/team766/robot/example/OI.java +++ b/src/main/java/com/team766/robot/example/OI.java @@ -5,23 +5,25 @@ import com.team766.hal.JoystickReader; import com.team766.hal.RobotProvider; import com.team766.logging.Category; -import com.team766.robot.example.procedures.*; /** * This class is the glue that binds the controls on the physical operator * interface to the code that allow control of the robot. */ public class OI extends Procedure { - private JoystickReader joystick0; - private JoystickReader joystick1; - private JoystickReader joystick2; + // input devices + private final JoystickReader leftJoystick; + private final JoystickReader gamepad; + + // OIFragments (driver, boxop, etc) + private final DriverOI driverOI; public OI() { loggerCategory = Category.OPERATOR_INTERFACE; - joystick0 = RobotProvider.instance.getJoystick(0); - joystick1 = RobotProvider.instance.getJoystick(1); - joystick2 = RobotProvider.instance.getJoystick(2); + leftJoystick = RobotProvider.instance.getJoystick(0); + gamepad = RobotProvider.instance.getJoystick(1); + driverOI = new DriverOI(leftJoystick); } public void run(final Context context) { @@ -30,8 +32,8 @@ public void run(final Context context) { context.waitFor(() -> RobotProvider.instance.hasNewDriverStationData()); RobotProvider.instance.refreshDriverStationData(); - // Add driver controls here - make sure to take/release ownership - // of mechanisms when appropriate. + // call each of the OI fragment's runOI methods. + driverOI.run(context); } } } diff --git a/src/main/java/com/team766/robot/example/constants/InputConstants.java b/src/main/java/com/team766/robot/example/constants/InputConstants.java new file mode 100644 index 000000000..ae21dc791 --- /dev/null +++ b/src/main/java/com/team766/robot/example/constants/InputConstants.java @@ -0,0 +1,11 @@ +package com.team766.robot.example.constants; + +public class InputConstants { + // this class only contains constants - should never be instantiated + private InputConstants() {} + + public static final int AXIS_X = 0; + public static final int AXIS_Y = 1; + + public static final int BUTTON_TRIGGER = 1; +} diff --git a/src/main/java/com/team766/robot/reva/BoxOpOI.java b/src/main/java/com/team766/robot/reva/BoxOpOI.java index 848187968..00f372a5e 100644 --- a/src/main/java/com/team766/robot/reva/BoxOpOI.java +++ b/src/main/java/com/team766/robot/reva/BoxOpOI.java @@ -19,9 +19,9 @@ public class BoxOpOI extends OIFragment { private final Shooter shooter; private final Climber climber; - private final OICondition shooterShoot; - private final OICondition intakeOut; - private final OICondition intakeIn; + private final Condition shooterShoot; + private final Condition intakeOut; + private final Condition intakeIn; public BoxOpOI( JoystickReader gamepad, @@ -35,13 +35,15 @@ public BoxOpOI( this.shooter = shooter; this.climber = climber; - shooterShoot = new OICondition(() -> gamepad.getAxis(InputConstants.XBOX_RT) > 0); - intakeOut = new OICondition(() -> gamepad.getButton(InputConstants.XBOX_RB)); - intakeIn = new OICondition(() -> gamepad.getButton(InputConstants.XBOX_LB)); + shooterShoot = new Condition(() -> gamepad.getAxis(InputConstants.XBOX_RT) > 0); + intakeOut = new Condition(() -> gamepad.getButton(InputConstants.XBOX_RB)); + intakeIn = new Condition(() -> gamepad.getButton(InputConstants.XBOX_LB)); } @Override protected void handleOI(Context context) { + evaluateConditions(); + // shoulder positions if (gamepad.getButtonPressed(InputConstants.XBOX_A)) { context.takeOwnership(shoulder); diff --git a/src/main/java/com/team766/robot/reva/DebugOI.java b/src/main/java/com/team766/robot/reva/DebugOI.java index 20cb173be..12bb0a6b8 100644 --- a/src/main/java/com/team766/robot/reva/DebugOI.java +++ b/src/main/java/com/team766/robot/reva/DebugOI.java @@ -39,11 +39,11 @@ public class DebugOI extends OIFragment { private final Climber climber; private final Intake intake; private final Shooter shooter; - private final OICondition controlShoulder; - private final OICondition controlClimber; - private final OICondition controlShooter; - private final OICondition intakeIn; - private final OICondition intakeOut; + private final Condition controlShoulder; + private final Condition controlClimber; + private final Condition controlShooter; + private final Condition intakeIn; + private final Condition intakeOut; public DebugOI( JoystickReader macropad, @@ -57,16 +57,17 @@ public DebugOI( this.intake = intake; this.shooter = shooter; - controlShoulder = - new OICondition(() -> macropad.getButton(InputConstants.CONTROL_SHOULDER)); - controlClimber = new OICondition(() -> macropad.getButton(InputConstants.CONTROL_CLIMBER)); - controlShooter = new OICondition(() -> macropad.getButton(InputConstants.CONTROL_SHOOTER)); - intakeIn = new OICondition(() -> macropad.getButton(InputConstants.INTAKE_IN)); - intakeOut = new OICondition(() -> macropad.getButton(InputConstants.INTAKE_OUT)); + controlShoulder = new Condition(() -> macropad.getButton(InputConstants.CONTROL_SHOULDER)); + controlClimber = new Condition(() -> macropad.getButton(InputConstants.CONTROL_CLIMBER)); + controlShooter = new Condition(() -> macropad.getButton(InputConstants.CONTROL_SHOOTER)); + intakeIn = new Condition(() -> macropad.getButton(InputConstants.INTAKE_IN)); + intakeOut = new Condition(() -> macropad.getButton(InputConstants.INTAKE_OUT)); } @Override protected void handleOI(Context context) { + evaluateConditions(); + // fine-grained control of the shoulder // used for testing and tuning // press down the shoulder control button and nudge the angle up and down diff --git a/src/main/java/com/team766/robot/reva/OI.java b/src/main/java/com/team766/robot/reva/OI.java index e2e1d9e38..137c50bee 100644 --- a/src/main/java/com/team766/robot/reva/OI.java +++ b/src/main/java/com/team766/robot/reva/OI.java @@ -48,11 +48,11 @@ public void run(Context context) { // of mechanisms when appropriate. // Driver OI: take input from left, right joysticks. control drive. - driverOI.runOI(context); + driverOI.run(context); // Debug OI: allow for finer-grain testing of each mechanism. - debugOI.runOI(context); + debugOI.run(context); - boxOpOI.runOI(context); + boxOpOI.run(context); } } }