diff --git a/docs.css b/docs.css index 799e680..c3de853 100644 --- a/docs.css +++ b/docs.css @@ -12,8 +12,8 @@ --block-font-family: var(--body-font-family); --code-font-family: monospace; /* Base font sizes for body and code elements */ - --body-font-size: 14px; - --code-font-size: 14px; + --body-font-size: 16px; + --code-font-size: 16px; /* Text colors for body and block elements */ --body-text-color: #353833; --block-text-color: #474747; @@ -921,12 +921,12 @@ li.ui-static-link a, li.ui-static-link a:visited { .search-tag-desc-result { font-style: italic; - font-size: 11px; + font-size: 16px; } .search-tag-holder-result { font-style: italic; - font-size: 12px; + font-size: 16px; } .search-tag-result:target { @@ -1525,7 +1525,6 @@ body { background-color: #ffffff; color: #141414; font-family: 'Roboto', sans-serif; - font-size: 76%; margin: 0; } @@ -1663,16 +1662,6 @@ section#field-summary, section#constructor-summary, section#method-summary, sect margin-left: -10px; } -section#field-detail, section#constructor-detail, section#method-detail, -#field-summary > div.caption, -#constructor-summary > div.caption, -#nested-class-summary > div.caption, -#all-packages-table > div.caption > span, -#class > div.caption > span, -#method > div.caption > span { - display: none; -} - #field-summary > h2, #constructor-summary > h2, #method-summary > h2, #nested-class-summary > h2 { font-style: normal; } @@ -1684,7 +1673,7 @@ body.class-declaration-page .summary .inherited-list h2 { background: none; border: none; font-weight: normal; - font-size: 15px; + font-size: 16px; } diff --git a/src/main/java/dev/vexide/hydrozoa/CompetitionRobot.java b/src/main/java/dev/vexide/hydrozoa/CompetitionRobot.java index c38e91c..b5f8ae0 100644 --- a/src/main/java/dev/vexide/hydrozoa/CompetitionRobot.java +++ b/src/main/java/dev/vexide/hydrozoa/CompetitionRobot.java @@ -1,36 +1,77 @@ package dev.vexide.hydrozoa; +import java.util.function.Function; + +/** + * A robot that can be used in a competition. Classes implementing this interface can be used with the competition + * runtime to control what the robot does in different states. + * + * @see CompetitionRuntime#start(Function) + */ public interface CompetitionRobot { + /** + * Called when the robot is connected to the field. + */ default void connected() { } + /** + * Called when the robot is disconnected from the field. + */ default void disconnected() { } + /** + * Called when the robot switches to the disabled mode. + */ default void disabledInit() { } + /** + * Called periodically while the robot is disabled. + */ default void disabledPeriodic() { } + /** + * Called when the robot exits the disabled mode. + */ default void disabledExit() { } + /** + * Called when the robot switches to the autonomous mode. + */ default void autonomousInit() { } + /** + * Called periodically while the robot is in the autonomous mode. + */ default void autonomousPeriodic() { } + /** + * Called when the robot exits the autonomous mode. + */ default void autonomousExit() { } + /** + * Called when the robot switches to the driver mode. + */ default void driverInit() { } + /** + * Called periodically while the robot is in the driver mode. + */ default void driverPeriodic() { } + /** + * Called when the robot exits the driver mode. + */ default void driverExit() { } } diff --git a/src/main/java/dev/vexide/hydrozoa/CompetitionRuntime.java b/src/main/java/dev/vexide/hydrozoa/CompetitionRuntime.java index 5ec044e..f280bc9 100644 --- a/src/main/java/dev/vexide/hydrozoa/CompetitionRuntime.java +++ b/src/main/java/dev/vexide/hydrozoa/CompetitionRuntime.java @@ -11,25 +11,47 @@ import java.util.Optional; import java.util.function.Function; +/** + * A runtime environment which manages the competition state and robot lifecycle. + */ public final class CompetitionRuntime { private CompetitionRuntime() { } + /** + * The default interval at which the robot will run its periodic methods. + */ public static final Duration DEFAULT_PERIOD = Duration.ofMillis(2); + private static @Nullable Boolean wasConnected = null; private static @Nullable Mode previousMode = null; + + /** + * The rate at which this robot will run its periodic methods. + */ public static @NotNull Duration period = DEFAULT_PERIOD; + /** + * Asserts that this robot is enabled. + * + * @throws DeviceException if this robot is disabled + */ public static void assertRobotEnabled() throws DeviceException { - if (!mode().equals(Mode.Driver)) { + if (mode().equals(Mode.Disabled)) { throw new RobotDisabledException(); } } + /** + * Starts the competition runtime, using the given function to create the robot instance. + * @param factory a function which creates a robot instance from the peripherals + * @throws IllegalStateException if the peripherals have already been taken or this method has already been called + */ public static void start(@NotNull Function<@NotNull Peripherals, @NotNull CompetitionRobot> factory) throws IllegalStateException { try { var robot = factory.apply(Peripherals.take().orElseThrow(() -> new IllegalStateException("Peripherals already taken"))); + //noinspection InfiniteLoopStatement while (true) { var begin = Instant.now(); @@ -79,30 +101,81 @@ public static void start(@NotNull Function<@NotNull Peripherals, @NotNull Compet } } + /** + * Gets the current competition status. + * @return the competition status + */ @Contract(value = "-> new", pure = true) public static @NotNull Status status() { return Status.fromBitflags(VexSdk.vexCompetitionStatus()); } + /** + * Determines whether this robot is connected to a competition control system (either a competition switch or + * field control). + * @return {@code true} if the robot is connected, {@code false} otherwise + */ @Contract(pure = true) public static boolean connected() { return status().connected(); } + /** + * Determines which competition system this robot is connected to. + * @return the competition system, or an empty optional if the robot is not connected to one + */ @Contract(pure = true) public static @NotNull Optional system() { return status().competitionSystem(); } + /** + * Determines the current mode of the robot's competition - disabled, autonomous, or driver. + * @return the current mode + */ public static @NotNull Mode mode() { return status().mode(); } + /** + * A competition system which the robot can be connected to. + */ public enum CompetitionSystem { + /** + * The robot is connected to a Smart Field Control system. + */ FieldControl, + /** + * The robot is connected to a competition switch. + */ CompetitionSwitch, } + /** + * An enumeration of the possible competition modes. + */ + public enum Mode { + /** + * The robot is disabled by field control. + */ + Disabled, + /** + * The robot is in autonomous mode. + */ + Autonomous, + /** + * The robot is in driver control mode. + */ + Driver, + } + + /** + * The status of the competition. + * @param disabled whether the robot is disabled by field control + * @param autonomous whether the robot is in autonomous mode + * @param connected whether the robot is connected to competition control + * @param system whether the robot is connected to field control + */ public record Status(boolean disabled, boolean autonomous, boolean connected, boolean system) { /** * Robot is disabled by field control. @@ -121,16 +194,29 @@ public record Status(boolean disabled, boolean autonomous, boolean connected, bo */ static final int SYSTEM = 1 << 3; + /** + * Creates a new competition status from bitflags. + * @param flags the bitflags representing the status + * @return the new competition status + */ @Contract(value = "_ -> new", pure = true) public static @NotNull Status fromBitflags(int flags) { return new Status((flags & DISABLED) != 0, (flags & AUTONOMOUS) != 0, (flags & CONNECTED) != 0, (flags & SYSTEM) != 0); } + /** + * Converts this competition status to bitflags. + * @return the bitflags representing the status + */ @Contract(pure = true) - public static int toBitflags(@NotNull Status status) { - return (status.disabled() ? DISABLED : 0) | (status.autonomous() ? AUTONOMOUS : 0) | (status.connected() ? CONNECTED : 0) | (status.system() ? SYSTEM : 0); + public int toBitflags() { + return (disabled() ? DISABLED : 0) | (autonomous() ? AUTONOMOUS : 0) | (connected() ? CONNECTED : 0) | (system() ? SYSTEM : 0); } + /** + * Determines the competition system that the robot is connected to. + * @return the competition system, or an empty optional if the robot is not connected + */ @Contract(pure = true) public @NotNull Optional competitionSystem() { if (connected()) { @@ -144,6 +230,10 @@ public static int toBitflags(@NotNull Status status) { } } + /** + * Determines the current mode of the robot's competition. + * @return the current mode + */ @Contract(pure = true) public @NotNull Mode mode() { if (disabled()) { @@ -155,10 +245,4 @@ public static int toBitflags(@NotNull Status status) { } } } - - public enum Mode { - Disabled, - Autonomous, - Driver, - } } diff --git a/src/main/java/dev/vexide/hydrozoa/Peripherals.java b/src/main/java/dev/vexide/hydrozoa/Peripherals.java index c9f98e5..68055db 100644 --- a/src/main/java/dev/vexide/hydrozoa/Peripherals.java +++ b/src/main/java/dev/vexide/hydrozoa/Peripherals.java @@ -10,6 +10,28 @@ import java.util.NoSuchElementException; import java.util.Optional; +/** + * A collection of all the peripherals that are available to the robot. + *

+ * Peripherals instances can be used to initialize the robot's hardware by using the various {@code take*} methods. + * It is important to understand that peripherals may only be removed from the Peripherals instance once. This helps + * to prevent the accidental duplication of hardware resources. + *

+ * For example, the following examples will throw exceptions because the port number 1 has already been used: + *

{@code
+ * var peripherals = Peripherals.take().orElseThrow();
+ *
+ * Motor leftMotor = new Motor(peripherals.takePort(1), Motor.Gearset.GREEN, Motor.Direction.FORWARD);
+ * Motor rightMotor = new Motor(peripherals.takePort(1), Motor.Gearset.GREEN, Motor.Direction.FORWARD);
+ * }
+ *
{@code
+ * var peripherals = Peripherals.take().orElseThrow();
+ *
+ * var port = peripherals.takePort(1);
+ * Motor leftMotor = new Motor(port, Motor.Gearset.GREEN, Motor.Direction.FORWARD);
+ * Motor rightMotor = new Motor(port, Motor.Gearset.GREEN, Motor.Direction.FORWARD);
+ * }
+ */ public class Peripherals { private static final Key key = new Key(); private static Peripherals instance = new Peripherals(); @@ -21,10 +43,15 @@ public class Peripherals { private Peripherals() { for (int i = 0; i < ports.length; i++) { - ports[i] = new SmartPort(key, i); + ports[i] = new SmartPort(key, i + 1); } } + /** + * Creates a new instance of the Peripherals class. This method may only be called once. + * + * @return a new instance of the Peripherals class, or an empty optional if this method has already been called + */ public static Optional take() { if (instance == null) { return Optional.empty(); @@ -35,14 +62,30 @@ public static Optional take() { return Optional.of(peripherals); } + /** + * Infallibly creates a new instance of the Peripherals class. Using this method can make your program + * susceptible to mistakes such as accidentally creating two motors that use the same smart port. + *

Conditions

+ *

+ * This method may not be used to allow multiple instances of smart devices that use the same port to exist + * simultaneously. + * @return a new instance of the Peripherals class + */ public static @NotNull Peripherals unsafelyCreate() { return new Peripherals(); } + /** + * Takes a smart port from this collection. + * @param portNumber the port number to take, as labeled on the robot brain + * @return the smart port + * @throws IllegalArgumentException if the port number is not in the range [1, 21] + * @throws NoSuchElementException if the port has already been removed + */ @Contract("_ -> new") public @NotNull SmartPort takePort(int portNumber) throws IllegalArgumentException, NoSuchElementException { - if (portNumber < 0 || portNumber >= ports.length) { - throw new IllegalArgumentException(String.format("Port %d is out of range [0, 21]", portNumber)); + if (portNumber <= 0 || portNumber > ports.length) { + throw new IllegalArgumentException(String.format("Port %d is out of range [1, 21]", portNumber)); } var port = ports[portNumber]; @@ -50,10 +93,16 @@ public static Optional take() { throw new NoSuchElementException(String.format("Port %d has already been used", portNumber)); } - ports[portNumber] = null; + ports[portNumber - 1] = null; return port; } + /** + * Takes a controller from this collection. + * @param id the ID of the controller to take + * @return the controller + * @throws NoSuchElementException if the controller with this ID has already been removed + */ @Contract("_ -> new") public @NotNull Controller takeController(@NotNull Controller.Id id) throws NoSuchElementException { Controller controller; @@ -66,26 +115,33 @@ public static Optional take() { } if (controller == null) { - throw new NoSuchElementException(String.format("%s controller has already been taken", id)); + throw new NoSuchElementException(String.format("%s controller has already been removed", id)); } return controller; } + /** + * Takes the display from this collection. + * @return the display + * @throws NoSuchElementException if the display has already been removed + */ @Contract("-> new") public @NotNull Display takeDisplay() throws NoSuchElementException { var display = this.display; this.display = null; if (display == null) { - throw new NoSuchElementException("The display has already been taken"); + throw new NoSuchElementException("The display has already been removed"); } return display; } + /** + * A unique key that is used to prevent the creation of peripheral devices outside the Peripherals class. + */ public static class Key { - Key() { - } + private Key() {} } } diff --git a/src/main/java/dev/vexide/hydrozoa/Platform.java b/src/main/java/dev/vexide/hydrozoa/Platform.java index ea76024..c7c3997 100644 --- a/src/main/java/dev/vexide/hydrozoa/Platform.java +++ b/src/main/java/dev/vexide/hydrozoa/Platform.java @@ -1,9 +1,18 @@ package dev.vexide.hydrozoa; import dev.vexide.hydrozoa.sdk.VexSdk; -import org.jetbrains.annotations.Contract; -public class Platform { +/** + * A utility class for platform-specific operations. + */ +public final class Platform { + private Platform() { + } + + /** + * Yields the current task so that background processing may run, sensors may be updated, and the serial port may be + * flushed. + */ public static void yield() { VexSdk.vexTasksRun(); } diff --git a/src/main/java/dev/vexide/hydrozoa/RobotDisabledException.java b/src/main/java/dev/vexide/hydrozoa/RobotDisabledException.java index ee025a2..315cc87 100644 --- a/src/main/java/dev/vexide/hydrozoa/RobotDisabledException.java +++ b/src/main/java/dev/vexide/hydrozoa/RobotDisabledException.java @@ -1,6 +1,13 @@ package dev.vexide.hydrozoa; +/** + * An exception that is thrown when an operation that requires the robot to be enabled is attempted while the robot is + * disabled. + */ public class RobotDisabledException extends DeviceException { + /** + * Create a new {@code RobotDisabledException}. + */ public RobotDisabledException() { super("Cannot perform this operation when the robot is disabled"); } diff --git a/src/main/java/dev/vexide/hydrozoa/devices/smart/Motor.java b/src/main/java/dev/vexide/hydrozoa/devices/smart/Motor.java index 3e8612b..5bf7629 100644 --- a/src/main/java/dev/vexide/hydrozoa/devices/smart/Motor.java +++ b/src/main/java/dev/vexide/hydrozoa/devices/smart/Motor.java @@ -34,6 +34,7 @@ public Motor(@NotNull SmartPort port, @NotNull Gearset gearset, @NotNull Directi this.gearset = gearset; this.direction = direction; + var handle = this.port.deviceHandle(); VexSdk.Motor.vexDeviceMotorEncoderUnitsSet(handle, V5MotorEncoderUnits.Counts); VexSdk.Motor.vexDeviceMotorReverseFlagSet(handle, direction.equals(Direction.REVERSE)); VexSdk.Motor.vexDeviceMotorGearingSet(handle, gearset.getRaw()); @@ -60,7 +61,7 @@ public Motor(@NotNull SmartPort port, @NotNull Gearset gearset, @NotNull Directi */ public void setGearset(@NotNull Gearset gearset) { this.gearset = gearset; - VexSdk.Motor.vexDeviceMotorGearingSet(handle, gearset.getRaw()); + VexSdk.Motor.vexDeviceMotorGearingSet(port.deviceHandle(), gearset.getRaw()); } /** @@ -79,7 +80,7 @@ public void setGearset(@NotNull Gearset gearset) { */ public void setDirection(@NotNull Direction direction) { this.direction = direction; - VexSdk.Motor.vexDeviceMotorReverseFlagSet(handle, direction.equals(Direction.REVERSE)); + VexSdk.Motor.vexDeviceMotorReverseFlagSet(port.deviceHandle(), direction.equals(Direction.REVERSE)); } /** diff --git a/src/main/java/dev/vexide/hydrozoa/devices/smart/MotorControl.java b/src/main/java/dev/vexide/hydrozoa/devices/smart/MotorControl.java index dde40aa..c164a19 100644 --- a/src/main/java/dev/vexide/hydrozoa/devices/smart/MotorControl.java +++ b/src/main/java/dev/vexide/hydrozoa/devices/smart/MotorControl.java @@ -10,6 +10,9 @@ * @see Motor#setTarget(MotorControl) */ public abstract class MotorControl { + MotorControl() { + } + abstract void apply(@NotNull Motor motor); /** @@ -43,8 +46,9 @@ void apply(@NotNull Motor motor) { // throw new IllegalArgumentException("Voltage must be in the range [-12, 12]"); // } - VexSdk.Motor.vexDeviceMotorBrakeModeSet(motor.handle, V5MotorBrakeMode.Coast); - VexSdk.Motor.vexDeviceMotorVoltageSet(motor.handle, (int) (getVoltage() * 1000)); + var handle = motor.port.deviceHandle(); + VexSdk.Motor.vexDeviceMotorBrakeModeSet(handle, V5MotorBrakeMode.Coast); + VexSdk.Motor.vexDeviceMotorVoltageSet(handle, (int) (getVoltage() * 1000)); } } @@ -74,8 +78,9 @@ public int getRpm() { @Override void apply(@NotNull Motor motor) { - VexSdk.Motor.vexDeviceMotorBrakeModeSet(motor.handle, V5MotorBrakeMode.Coast); - VexSdk.Motor.vexDeviceMotorVelocitySet(motor.handle, getRpm()); + var handle = motor.port.deviceHandle(); + VexSdk.Motor.vexDeviceMotorBrakeModeSet(handle, V5MotorBrakeMode.Coast); + VexSdk.Motor.vexDeviceMotorVelocitySet(handle, getRpm()); } } @@ -117,9 +122,10 @@ public int getVelocity() { @Override void apply(@NotNull Motor motor) { - VexSdk.Motor.vexDeviceMotorBrakeModeSet(motor.handle, V5MotorBrakeMode.Coast); + var handle = motor.port.deviceHandle(); + VexSdk.Motor.vexDeviceMotorBrakeModeSet(handle, V5MotorBrakeMode.Coast); VexSdk.Motor.vexDeviceMotorAbsoluteTargetSet( - motor.handle, + handle, getPosition().ticks(motor.getGearset().getTicksPerRevolution()), getVelocity() ); @@ -152,8 +158,9 @@ public Brake(@NotNull Motor.BrakeMode mode) { @Override void apply(@NotNull Motor motor) { - VexSdk.Motor.vexDeviceMotorBrakeModeSet(motor.handle, getMode().getRaw()); - VexSdk.Motor.vexDeviceMotorVelocitySet(motor.handle, 0); + var handle = motor.port.deviceHandle(); + VexSdk.Motor.vexDeviceMotorBrakeModeSet(handle, getMode().getRaw()); + VexSdk.Motor.vexDeviceMotorVelocitySet(handle, 0); } } } diff --git a/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartDevice.java b/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartDevice.java index 0aa6f6c..0966225 100644 --- a/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartDevice.java +++ b/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartDevice.java @@ -1,7 +1,6 @@ package dev.vexide.hydrozoa.devices.smart; import dev.vexide.hydrozoa.DeviceException; -import dev.vexide.hydrozoa.sdk.V5_Device; import dev.vexide.hydrozoa.sdk.V5_DeviceType; import org.jetbrains.annotations.NotNull; @@ -19,16 +18,24 @@ public abstract class SmartDevice { public static final Duration UPDATE_INTERVAL = Duration.ofMillis(10); final @NotNull SmartPort port; - final @NotNull V5_Device handle; /** * Creates a new generic smart device. * - * @param port the smart port which this device is connected to + * @param port The smart port which this device is connected to. The port will be consumed by this device and may + * not be reused. */ public SmartDevice(@NotNull SmartPort port) { - this.port = port; - handle = port.deviceHandle(); + this.port = port.take(); + } + + /** + * Remove the smart port handle from this smart device, invalidating this device. + * + * @return a new smart port with the same port number as this device + */ + public @NotNull SmartPort takePort() { + return port.take(); } /** diff --git a/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartPort.java b/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartPort.java index fd773f2..3734f01 100644 --- a/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartPort.java +++ b/src/main/java/dev/vexide/hydrozoa/devices/smart/SmartPort.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.Optional; @@ -19,19 +20,34 @@ */ public final class SmartPort { private final int number; + private @Nullable Peripherals.Key key; /** * Creates a new Smart Port. * - * @param ignoredKey the peripherals key, which may only be accessed from within the {@link Peripherals} class + * @param key the peripherals key, which may only be accessed from within the {@link Peripherals} class * @param number the port number of the smart port * @see Peripherals#takePort(int) */ @ApiStatus.Internal - public SmartPort(Peripherals.Key ignoredKey, int number) { + public SmartPort(@NotNull Peripherals.Key key, int number) { + this.key = key; this.number = number; } + /** + * Create a duplicate of this smart port while consuming this instance. This method is called during the + * instantiation of {@link SmartDevice} objects to ensure that each smart device has its own unique smart port. + * + * @return a new smart port with the same port number + */ + @Contract("-> new") + public @NotNull SmartPort take() { + var key = validate(); + this.key = null; + return new SmartPort(key, number); + } + /** * Gets the port number of this smart port. * @return the port number, as labeled on the robot brain @@ -50,13 +66,23 @@ public int getIndex() { return number - 1; } + @Contract(value = "-> _") + private @NotNull Peripherals.Key validate() { + if (key == null) { + throw new IllegalStateException(String.format("This smart port (%d) is already being used by another smart device", number)); + } + return this.key; + } + /** * Gets the underlying device handle for this smart port so that it can be passed to the {@link VexSdk}. * @return the device's SDK handle + * @throws IllegalStateException if the smart port is already being used by another smart device */ @Contract(" -> new") @NotNull V5_Device deviceHandle() { + validate(); return VexSdk.Device.vexDeviceGetByIndex(getIndex()); } diff --git a/src/main/java/dev/vexide/hydrozoa/display/Font.java b/src/main/java/dev/vexide/hydrozoa/display/Font.java index 3492df6..805c0c8 100644 --- a/src/main/java/dev/vexide/hydrozoa/display/Font.java +++ b/src/main/java/dev/vexide/hydrozoa/display/Font.java @@ -9,7 +9,7 @@ * * @param size the size of the font * @param family the font family of the font - * @see Text#Text(String, Font, int, int) + * @see Text#Text(String, int, int, Font) */ public record Font(@NotNull Size size, @NotNull Family family) { /** diff --git a/src/main/java/dev/vexide/hydrozoa/package-info.java b/src/main/java/dev/vexide/hydrozoa/package-info.java new file mode 100644 index 0000000..cdae9fc --- /dev/null +++ b/src/main/java/dev/vexide/hydrozoa/package-info.java @@ -0,0 +1,4 @@ +/** + * The main package for the Hydrozoa project, which contains the basics for configuring and running a robot. + */ +package dev.vexide.hydrozoa; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a015047..29157cd 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,6 @@ +/** + * The Hydrozoa Java SDK is a software library that allows Java and Kotlin programs to control VEX V5 robots. + */ module dev.vexide.hydrozoa { requires org.jetbrains.annotations; requires dev.vexide.hydrozoa.sdk;