mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-24 01:31:46 +00:00
Remove most 2022 deprecations (#4205)
Excludes "old" commands and SimDevice functions.
This commit is contained in:
@@ -62,34 +62,6 @@ public class Compressor implements Sendable, AutoCloseable {
|
||||
m_module = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the compressor running in closed loop control mode.
|
||||
*
|
||||
* <p>Use the method in cases where you would like to manually stop and start the compressor for
|
||||
* applications such as conserving battery or making sure that the compressor motor doesn't start
|
||||
* during critical operations.
|
||||
*
|
||||
* @deprecated Use enableDigital() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public void start() {
|
||||
enableDigital();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the compressor from running in closed loop control mode.
|
||||
*
|
||||
* <p>Use the method in cases where you would like to manually stop and start the compressor for
|
||||
* applications such as conserving battery or making sure that the compressor motor doesn't start
|
||||
* during critical operations.
|
||||
*
|
||||
* @deprecated Use disable() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public void stop() {
|
||||
disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of the compressor. To (re)enable the compressor use enableDigital() or
|
||||
* enableAnalog(...).
|
||||
|
||||
@@ -398,8 +398,6 @@ public final class DriverStation {
|
||||
final JoystickLogSender[] m_joysticks;
|
||||
}
|
||||
|
||||
private static DriverStation instance = new DriverStation();
|
||||
|
||||
// Joystick User Data
|
||||
private static HALJoystickAxes[] m_joystickAxes = new HALJoystickAxes[kJoystickPorts];
|
||||
private static HALJoystickPOVs[] m_joystickPOVs = new HALJoystickPOVs[kJoystickPorts];
|
||||
@@ -448,17 +446,6 @@ public final class DriverStation {
|
||||
private static final ControlWord m_controlWordCache;
|
||||
private static long m_lastControlWordUpdate;
|
||||
|
||||
/**
|
||||
* Gets an instance of the DriverStation.
|
||||
*
|
||||
* @return The DriverStation.
|
||||
* @deprecated Use the static methods
|
||||
*/
|
||||
@Deprecated
|
||||
public static DriverStation getInstance() {
|
||||
return DriverStation.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* DriverStation constructor.
|
||||
*
|
||||
@@ -985,18 +972,6 @@ public final class DriverStation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether the Driver Station requires the robot to be running in
|
||||
* operator-controlled mode.
|
||||
*
|
||||
* @return True if operator-controlled mode should be enabled, false otherwise.
|
||||
* @deprecated Use isTeleop() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public static boolean isOperatorControl() {
|
||||
return isTeleop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether the Driver Station requires the robot to be running in
|
||||
* operator-controlled mode.
|
||||
@@ -1007,18 +982,6 @@ public final class DriverStation {
|
||||
return !(isAutonomous() || isTest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether the Driver Station requires the robot to be running in
|
||||
* operator-controller mode and enabled.
|
||||
*
|
||||
* @return True if operator-controlled mode should be set and the robot should be enabled.
|
||||
* @deprecated Use isTeleopEnabled() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public static boolean isOperatorControlEnabled() {
|
||||
return isTeleopEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether the Driver Station requires the robot to be running in
|
||||
* operator-controller mode and enabled.
|
||||
@@ -1334,18 +1297,6 @@ public final class DriverStation {
|
||||
m_userInAutonomous = entering;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only to be used to tell the Driver Station what code you claim to be executing for diagnostic
|
||||
* purposes only.
|
||||
*
|
||||
* @param entering If true, starting teleop code; if false, leaving teleop code
|
||||
* @deprecated Use {@link #inTeleop(boolean)} instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public static void inOperatorControl(boolean entering) {
|
||||
m_userInTeleop = entering;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only to be used to tell the Driver Station what code you claim to be executing for diagnostic
|
||||
* purposes only.
|
||||
|
||||
@@ -370,8 +370,11 @@ public class Encoder implements CounterBase, Sendable, AutoCloseable {
|
||||
*
|
||||
* @param maxPeriod The maximum time between rising and falling edges before the FPGA will report
|
||||
* the device stopped. This is expressed in seconds.
|
||||
* @deprecated Use setMinRate() in favor of this method. This takes unscaled periods and
|
||||
* setMinRate() scales using value from setDistancePerPulse().
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setMaxPeriod(double maxPeriod) {
|
||||
EncoderJNI.setEncoderMaxPeriod(m_encoder, maxPeriod);
|
||||
}
|
||||
|
||||
@@ -30,25 +30,9 @@ import java.util.Collection;
|
||||
public final class Preferences {
|
||||
/** The Preferences table name. */
|
||||
private static final String TABLE_NAME = "Preferences";
|
||||
/** The singleton instance. */
|
||||
private static Preferences instance;
|
||||
/** The network table. */
|
||||
private static final NetworkTable m_table;
|
||||
|
||||
/**
|
||||
* Returns the preferences instance.
|
||||
*
|
||||
* @return the preferences instance
|
||||
* @deprecated Use the static methods
|
||||
*/
|
||||
@Deprecated
|
||||
public static synchronized Preferences getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new Preferences();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** Creates a preference class. */
|
||||
private Preferences() {}
|
||||
|
||||
@@ -87,19 +71,6 @@ public final class Preferences {
|
||||
entry.setPersistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given string into the preferences table.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @throws NullPointerException if value is null
|
||||
* @deprecated Use {@link #setString(String, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void putString(String key, String value) {
|
||||
setString(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given string into the preferences table if it doesn't already exist.
|
||||
*
|
||||
@@ -124,18 +95,6 @@ public final class Preferences {
|
||||
entry.setPersistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given int into the preferences table.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @deprecated Use {@link #setInt(String, int)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void putInt(String key, int value) {
|
||||
setInt(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given int into the preferences table if it doesn't already exist.
|
||||
*
|
||||
@@ -160,18 +119,6 @@ public final class Preferences {
|
||||
entry.setPersistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given double into the preferences table.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @deprecated Use {@link #setDouble(String, double)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void putDouble(String key, double value) {
|
||||
setDouble(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given double into the preferences table if it doesn't already exist.
|
||||
*
|
||||
@@ -196,18 +143,6 @@ public final class Preferences {
|
||||
entry.setPersistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given float into the preferences table.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @deprecated Use {@link #setFloat(String, float)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void putFloat(String key, float value) {
|
||||
setFloat(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given float into the preferences table if it doesn't already exist.
|
||||
*
|
||||
@@ -232,18 +167,6 @@ public final class Preferences {
|
||||
entry.setPersistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given boolean into the preferences table.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @deprecated Use {@link #setBoolean(String, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void putBoolean(String key, boolean value) {
|
||||
setBoolean(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given boolean into the preferences table if it doesn't already exist.
|
||||
*
|
||||
@@ -268,18 +191,6 @@ public final class Preferences {
|
||||
entry.setPersistent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given long into the preferences table.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @deprecated Use {@link #setLong(String, long)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void putLong(String key, long value) {
|
||||
setLong(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given long into the preferences table if it doesn't already exist.
|
||||
*
|
||||
|
||||
@@ -234,18 +234,6 @@ public abstract class RobotBase implements AutoCloseable {
|
||||
return DriverStation.isTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the robot is currently in Operator Control mode as determined by the field
|
||||
* controls.
|
||||
*
|
||||
* @return True if the robot is currently operating in Tele-Op mode.
|
||||
* @deprecated Use isTeleop() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public boolean isOperatorControl() {
|
||||
return DriverStation.isTeleop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the robot is currently in Operator Control mode as determined by the field
|
||||
* controls.
|
||||
@@ -256,18 +244,6 @@ public abstract class RobotBase implements AutoCloseable {
|
||||
return DriverStation.isTeleop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the robot is current in Operator Control mode and enabled as determined by the
|
||||
* field controls.
|
||||
*
|
||||
* @return True if the robot is currently operating in Tele-Op mode while enabled.
|
||||
* @deprecated Use isTeleopEnabled() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public boolean isOperatorControlEnabled() {
|
||||
return DriverStation.isTeleopEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the robot is current in Operator Control mode and enabled as determined by the
|
||||
* field controls.
|
||||
|
||||
@@ -18,11 +18,6 @@ public final class RobotState {
|
||||
return DriverStation.isEStopped();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isOperatorControl() {
|
||||
return isTeleop();
|
||||
}
|
||||
|
||||
public static boolean isTeleop() {
|
||||
return DriverStation.isTeleop();
|
||||
}
|
||||
|
||||
@@ -123,30 +123,6 @@ public class SPI implements AutoCloseable {
|
||||
SPIJNI.spiSetOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure that the data is stable on the falling edge and the data changes on the rising edge.
|
||||
* Note this gets reversed is setClockActiveLow is set.
|
||||
*
|
||||
* @deprecated use {@link #setSampleDataOnTrailingEdge()} in most cases.
|
||||
*/
|
||||
@Deprecated
|
||||
public final void setSampleDataOnFalling() {
|
||||
m_sampleOnTrailing = 1;
|
||||
SPIJNI.spiSetOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure that the data is stable on the rising edge and the data changes on the falling edge.
|
||||
* Note this gets reversed is setClockActiveLow is set.
|
||||
*
|
||||
* @deprecated use {@link #setSampleDataOnLeadingEdge()} in most cases.
|
||||
*/
|
||||
@Deprecated
|
||||
public final void setSampleDataOnRising() {
|
||||
m_sampleOnTrailing = 0;
|
||||
SPIJNI.spiSetOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
|
||||
}
|
||||
|
||||
/** Configure the chip select line to be active high. */
|
||||
public final void setChipSelectActiveHigh() {
|
||||
SPIJNI.spiSetChipSelectActiveHigh(m_port);
|
||||
|
||||
@@ -81,48 +81,6 @@ public class SerialPort implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a Serial Port class.
|
||||
*
|
||||
* <p>Prefer to use the constructor that doesn't take a port name, but in some cases the automatic
|
||||
* detection might not work correctly.
|
||||
*
|
||||
* @param baudRate The baud rate to configure the serial port.
|
||||
* @param port The Serial port to use
|
||||
* @param portName The direct portName to use
|
||||
* @param dataBits The number of data bits per transfer. Valid values are between 5 and 8 bits.
|
||||
* @param parity Select the type of parity checking to use.
|
||||
* @param stopBits The number of stop bits to use as defined by the enum StopBits.
|
||||
* @deprecated Will be removed for 2019
|
||||
*/
|
||||
@Deprecated
|
||||
public SerialPort(
|
||||
final int baudRate,
|
||||
String portName,
|
||||
Port port,
|
||||
final int dataBits,
|
||||
Parity parity,
|
||||
StopBits stopBits) {
|
||||
m_portHandle = SerialPortJNI.serialInitializePortDirect((byte) port.value, portName);
|
||||
SerialPortJNI.serialSetBaudRate(m_portHandle, baudRate);
|
||||
SerialPortJNI.serialSetDataBits(m_portHandle, (byte) dataBits);
|
||||
SerialPortJNI.serialSetParity(m_portHandle, (byte) parity.value);
|
||||
SerialPortJNI.serialSetStopBits(m_portHandle, (byte) stopBits.value);
|
||||
|
||||
// Set the default read buffer size to 1 to return bytes immediately
|
||||
setReadBufferSize(1);
|
||||
|
||||
// Set the default timeout to 5 seconds.
|
||||
setTimeout(5.0);
|
||||
|
||||
// Don't wait until the buffer is full to transmit.
|
||||
setWriteBufferMode(WriteBufferMode.kFlushOnAccess);
|
||||
|
||||
disableTermination();
|
||||
|
||||
HAL.report(tResourceType.kResourceType_SerialPort, port.value + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a Serial Port class.
|
||||
*
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
/**
|
||||
* Interface for motor controlling devices.
|
||||
*
|
||||
* @deprecated Use {@link edu.wpi.first.wpilibj.motorcontrol.MotorController}.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public interface SpeedController {
|
||||
/**
|
||||
* Common interface for setting the speed of a motor controller.
|
||||
*
|
||||
* @param speed The speed to set. Value should be between -1.0 and 1.0.
|
||||
*/
|
||||
void set(double speed);
|
||||
|
||||
/**
|
||||
* Sets the voltage output of the MotorController. Compensates for the current bus voltage to
|
||||
* ensure that the desired voltage is output even if the battery voltage is below 12V - highly
|
||||
* useful when the voltage outputs are "meaningful" (e.g. they come from a feedforward
|
||||
* calculation).
|
||||
*
|
||||
* <p>NOTE: This function *must* be called regularly in order for voltage compensation to work
|
||||
* properly - unlike the ordinary set function, it is not "set it and forget it."
|
||||
*
|
||||
* @param outputVolts The voltage to output.
|
||||
*/
|
||||
default void setVoltage(double outputVolts) {
|
||||
set(outputVolts / RobotController.getBatteryVoltage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for getting the current set speed of a motor controller.
|
||||
*
|
||||
* @return The current set speed. Value is between -1.0 and 1.0.
|
||||
*/
|
||||
double get();
|
||||
|
||||
/**
|
||||
* Common interface for inverting direction of a motor controller.
|
||||
*
|
||||
* @param isInverted The state of inversion true is inverted.
|
||||
*/
|
||||
void setInverted(boolean isInverted);
|
||||
|
||||
/**
|
||||
* Common interface for returning if a motor controller is in the inverted state or not.
|
||||
*
|
||||
* @return isInverted The state of the inversion true is inverted.
|
||||
*/
|
||||
boolean getInverted();
|
||||
|
||||
/** Disable the motor controller. */
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* Stops motor movement. Motor can be moved again by calling set without having to re-enable the
|
||||
* motor.
|
||||
*/
|
||||
void stopMotor();
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj;
|
||||
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Allows multiple {@link SpeedController} objects to be linked together.
|
||||
*
|
||||
* @deprecated Use {@link edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup}.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public class SpeedControllerGroup implements MotorController, Sendable, AutoCloseable {
|
||||
private boolean m_isInverted;
|
||||
private final SpeedController[] m_speedControllers;
|
||||
private static int instances;
|
||||
|
||||
/**
|
||||
* Create a new SpeedControllerGroup with the provided SpeedControllers.
|
||||
*
|
||||
* @param speedController The first SpeedController to add.
|
||||
* @param speedControllers The SpeedControllers to add
|
||||
*/
|
||||
public SpeedControllerGroup(
|
||||
SpeedController speedController, SpeedController... speedControllers) {
|
||||
m_speedControllers = new SpeedController[speedControllers.length + 1];
|
||||
m_speedControllers[0] = speedController;
|
||||
System.arraycopy(speedControllers, 0, m_speedControllers, 1, speedControllers.length);
|
||||
init();
|
||||
}
|
||||
|
||||
public SpeedControllerGroup(SpeedController[] speedControllers) {
|
||||
m_speedControllers = Arrays.copyOf(speedControllers, speedControllers.length);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
for (SpeedController controller : m_speedControllers) {
|
||||
SendableRegistry.addChild(this, controller);
|
||||
}
|
||||
instances++;
|
||||
SendableRegistry.addLW(this, "MotorControllerGroup", instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(double speed) {
|
||||
for (SpeedController speedController : m_speedControllers) {
|
||||
speedController.set(m_isInverted ? -speed : speed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double get() {
|
||||
if (m_speedControllers.length > 0) {
|
||||
return m_speedControllers[0].get() * (m_isInverted ? -1 : 1);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInverted(boolean isInverted) {
|
||||
m_isInverted = isInverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getInverted() {
|
||||
return m_isInverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
for (SpeedController speedController : m_speedControllers) {
|
||||
speedController.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopMotor() {
|
||||
for (SpeedController speedController : m_speedControllers) {
|
||||
speedController.stopMotor();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Motor Controller");
|
||||
builder.setActuator(true);
|
||||
builder.setSafeState(this::stopMotor);
|
||||
builder.addDoubleProperty("Value", this::get, this::set);
|
||||
}
|
||||
}
|
||||
@@ -121,20 +121,6 @@ public class Timer {
|
||||
return get() >= seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the period specified has passed and if it has, advance the start time by that period.
|
||||
* This is useful to decide if it's time to do periodic work without drifting later by the time it
|
||||
* took to get around to checking.
|
||||
*
|
||||
* @param period The period to check for (in seconds).
|
||||
* @return Whether the period has passed.
|
||||
* @deprecated Use advanceIfElapsed() instead.
|
||||
*/
|
||||
@Deprecated(since = "2022", forRemoval = true)
|
||||
public boolean hasPeriodPassed(double period) {
|
||||
return advanceIfElapsed(period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the period specified has passed and if it has, advance the start time by that period.
|
||||
* This is useful to decide if it's time to do periodic work without drifting later by the time it
|
||||
|
||||
@@ -13,7 +13,7 @@ import edu.wpi.first.math.MathUtil;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import edu.wpi.first.wpilibj.SpeedController;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
|
||||
/**
|
||||
* A class for driving differential drive/skid-steer drive platforms such as the Kit of Parts drive
|
||||
@@ -87,12 +87,11 @@ import edu.wpi.first.wpilibj.SpeedController;
|
||||
* <p>{@link edu.wpi.first.wpilibj.MotorSafety} is enabled by default. The tankDrive, arcadeDrive,
|
||||
* or curvatureDrive methods should be called periodically to avoid Motor Safety timeouts.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoCloseable {
|
||||
private static int instances;
|
||||
|
||||
private final SpeedController m_leftMotor;
|
||||
private final SpeedController m_rightMotor;
|
||||
private final MotorController m_leftMotor;
|
||||
private final MotorController m_rightMotor;
|
||||
|
||||
private boolean m_reported;
|
||||
|
||||
@@ -131,7 +130,7 @@ public class DifferentialDrive extends RobotDriveBase implements Sendable, AutoC
|
||||
* @param leftMotor Left motor.
|
||||
* @param rightMotor Right motor.
|
||||
*/
|
||||
public DifferentialDrive(SpeedController leftMotor, SpeedController rightMotor) {
|
||||
public DifferentialDrive(MotorController leftMotor, MotorController rightMotor) {
|
||||
requireNonNull(leftMotor, "Left motor cannot be null");
|
||||
requireNonNull(rightMotor, "Right motor cannot be null");
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import edu.wpi.first.math.MathUtil;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import edu.wpi.first.wpilibj.SpeedController;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
|
||||
/**
|
||||
* A class for driving Killough drive platforms.
|
||||
@@ -43,7 +43,6 @@ import edu.wpi.first.wpilibj.SpeedController;
|
||||
* <p>{@link edu.wpi.first.wpilibj.MotorSafety} is enabled by default. The driveCartesian or
|
||||
* drivePolar methods should be called periodically to avoid Motor Safety timeouts.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class KilloughDrive extends RobotDriveBase implements Sendable, AutoCloseable {
|
||||
public static final double kDefaultLeftMotorAngle = 60.0;
|
||||
public static final double kDefaultRightMotorAngle = 120.0;
|
||||
@@ -51,9 +50,9 @@ public class KilloughDrive extends RobotDriveBase implements Sendable, AutoClose
|
||||
|
||||
private static int instances;
|
||||
|
||||
private SpeedController m_leftMotor;
|
||||
private SpeedController m_rightMotor;
|
||||
private SpeedController m_backMotor;
|
||||
private MotorController m_leftMotor;
|
||||
private MotorController m_rightMotor;
|
||||
private MotorController m_backMotor;
|
||||
|
||||
private Vector2d m_leftVec;
|
||||
private Vector2d m_rightVec;
|
||||
@@ -102,7 +101,7 @@ public class KilloughDrive extends RobotDriveBase implements Sendable, AutoClose
|
||||
* @param backMotor The motor on the back corner.
|
||||
*/
|
||||
public KilloughDrive(
|
||||
SpeedController leftMotor, SpeedController rightMotor, SpeedController backMotor) {
|
||||
MotorController leftMotor, MotorController rightMotor, MotorController backMotor) {
|
||||
this(
|
||||
leftMotor,
|
||||
rightMotor,
|
||||
@@ -125,9 +124,9 @@ public class KilloughDrive extends RobotDriveBase implements Sendable, AutoClose
|
||||
* @param backMotorAngle The angle of the back wheel's forward direction of travel.
|
||||
*/
|
||||
public KilloughDrive(
|
||||
SpeedController leftMotor,
|
||||
SpeedController rightMotor,
|
||||
SpeedController backMotor,
|
||||
MotorController leftMotor,
|
||||
MotorController rightMotor,
|
||||
MotorController backMotor,
|
||||
double leftMotorAngle,
|
||||
double rightMotorAngle,
|
||||
double backMotorAngle) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import edu.wpi.first.math.MathUtil;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import edu.wpi.first.wpilibj.SpeedController;
|
||||
import edu.wpi.first.wpilibj.motorcontrol.MotorController;
|
||||
|
||||
/**
|
||||
* A class for driving Mecanum drive platforms.
|
||||
@@ -50,14 +50,13 @@ import edu.wpi.first.wpilibj.SpeedController;
|
||||
* <p>{@link edu.wpi.first.wpilibj.MotorSafety} is enabled by default. The driveCartesian or
|
||||
* drivePolar methods should be called periodically to avoid Motor Safety timeouts.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class MecanumDrive extends RobotDriveBase implements Sendable, AutoCloseable {
|
||||
private static int instances;
|
||||
|
||||
private final SpeedController m_frontLeftMotor;
|
||||
private final SpeedController m_rearLeftMotor;
|
||||
private final SpeedController m_frontRightMotor;
|
||||
private final SpeedController m_rearRightMotor;
|
||||
private final MotorController m_frontLeftMotor;
|
||||
private final MotorController m_rearLeftMotor;
|
||||
private final MotorController m_frontRightMotor;
|
||||
private final MotorController m_rearRightMotor;
|
||||
|
||||
private boolean m_reported;
|
||||
|
||||
@@ -103,10 +102,10 @@ public class MecanumDrive extends RobotDriveBase implements Sendable, AutoClosea
|
||||
* @param rearRightMotor The motor on the rear-right corner.
|
||||
*/
|
||||
public MecanumDrive(
|
||||
SpeedController frontLeftMotor,
|
||||
SpeedController rearLeftMotor,
|
||||
SpeedController frontRightMotor,
|
||||
SpeedController rearRightMotor) {
|
||||
MotorController frontLeftMotor,
|
||||
MotorController rearLeftMotor,
|
||||
MotorController frontRightMotor,
|
||||
MotorController rearRightMotor) {
|
||||
requireNonNull(frontLeftMotor, "Front-left motor cannot be null");
|
||||
requireNonNull(rearLeftMotor, "Rear-left motor cannot be null");
|
||||
requireNonNull(frontRightMotor, "Front-right motor cannot be null");
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
package edu.wpi.first.wpilibj.drive;
|
||||
|
||||
import edu.wpi.first.math.MathUtil;
|
||||
import edu.wpi.first.wpilibj.MotorSafety;
|
||||
|
||||
/**
|
||||
@@ -81,20 +80,6 @@ public abstract class RobotDriveBase extends MotorSafety {
|
||||
@Override
|
||||
public abstract String getDescription();
|
||||
|
||||
/**
|
||||
* Returns 0.0 if the given value is within the specified range around zero. The remaining range
|
||||
* between the deadband and 1.0 is scaled from 0.0 to 1.0.
|
||||
*
|
||||
* @param value value to clip
|
||||
* @param deadband range around zero
|
||||
* @return The value after the deadband is applied.
|
||||
* @deprecated Use MathUtil.applyDeadband(double,double).
|
||||
*/
|
||||
@Deprecated(since = "2021", forRemoval = true)
|
||||
protected static double applyDeadband(double value, double deadband) {
|
||||
return MathUtil.applyDeadband(value, deadband);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize all wheel speeds if the magnitude of any wheel is greater than 1.0.
|
||||
*
|
||||
|
||||
@@ -5,17 +5,14 @@
|
||||
package edu.wpi.first.wpilibj.motorcontrol;
|
||||
|
||||
import edu.wpi.first.wpilibj.RobotController;
|
||||
import edu.wpi.first.wpilibj.SpeedController;
|
||||
|
||||
/** Interface for motor controlling devices. */
|
||||
@SuppressWarnings("removal")
|
||||
public interface MotorController extends SpeedController {
|
||||
public interface MotorController {
|
||||
/**
|
||||
* Common interface for setting the speed of a motor controller.
|
||||
*
|
||||
* @param speed The speed to set. Value should be between -1.0 and 1.0.
|
||||
*/
|
||||
@Override
|
||||
void set(double speed);
|
||||
|
||||
/**
|
||||
@@ -29,7 +26,6 @@ public interface MotorController extends SpeedController {
|
||||
*
|
||||
* @param outputVolts The voltage to output.
|
||||
*/
|
||||
@Override
|
||||
default void setVoltage(double outputVolts) {
|
||||
set(outputVolts / RobotController.getBatteryVoltage());
|
||||
}
|
||||
@@ -39,7 +35,6 @@ public interface MotorController extends SpeedController {
|
||||
*
|
||||
* @return The current set speed. Value is between -1.0 and 1.0.
|
||||
*/
|
||||
@Override
|
||||
double get();
|
||||
|
||||
/**
|
||||
@@ -47,7 +42,6 @@ public interface MotorController extends SpeedController {
|
||||
*
|
||||
* @param isInverted The state of inversion true is inverted.
|
||||
*/
|
||||
@Override
|
||||
void setInverted(boolean isInverted);
|
||||
|
||||
/**
|
||||
@@ -55,17 +49,14 @@ public interface MotorController extends SpeedController {
|
||||
*
|
||||
* @return isInverted The state of the inversion true is inverted.
|
||||
*/
|
||||
@Override
|
||||
boolean getInverted();
|
||||
|
||||
/** Disable the motor controller. */
|
||||
@Override
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* Stops motor movement. Motor can be moved again by calling set without having to re-enable the
|
||||
* motor.
|
||||
*/
|
||||
@Override
|
||||
void stopMotor();
|
||||
}
|
||||
|
||||
@@ -426,14 +426,7 @@ public class DifferentialDrivetrainSim {
|
||||
public enum KitbotWheelSize {
|
||||
kSixInch(Units.inchesToMeters(6)),
|
||||
kEightInch(Units.inchesToMeters(8)),
|
||||
kTenInch(Units.inchesToMeters(10)),
|
||||
|
||||
@Deprecated
|
||||
SixInch(Units.inchesToMeters(6)),
|
||||
@Deprecated
|
||||
EightInch(Units.inchesToMeters(8)),
|
||||
@Deprecated
|
||||
TenInch(Units.inchesToMeters(10));
|
||||
kTenInch(Units.inchesToMeters(10));
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final double value;
|
||||
|
||||
@@ -69,18 +69,6 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
|
||||
m_map.put(name, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given object to the list of options.
|
||||
*
|
||||
* @param name the name of the option
|
||||
* @param object the option
|
||||
* @deprecated Use {@link #addOption(String, Object)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void addObject(String name, V object) {
|
||||
addOption(name, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given object to the list of options and marks it as the default. Functionally, this is
|
||||
* very close to {@link #addOption(String, Object)} except that it will use this as the default
|
||||
@@ -96,18 +84,6 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
|
||||
addOption(name, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given object to the list of options and marks it as the default.
|
||||
*
|
||||
* @param name the name of the option
|
||||
* @param object the option
|
||||
* @deprecated Use {@link #setDefaultOption(String, Object)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void addDefault(String name, V object) {
|
||||
setDefaultOption(name, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected option. If there is none selected, it will return the default. If there is
|
||||
* none selected and no default, then it will return {@code null}.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj.vision;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
* A vision pipeline is responsible for running a group of OpenCV algorithms to extract data from an
|
||||
* image.
|
||||
*
|
||||
* @see VisionRunner
|
||||
* @see VisionThread
|
||||
* @deprecated Replaced with edu.wpi.first.vision.VisionPipeline
|
||||
*/
|
||||
@Deprecated
|
||||
public interface VisionPipeline {
|
||||
/**
|
||||
* Processes the image input and sets the result objects. Implementations should make these
|
||||
* objects accessible.
|
||||
*
|
||||
* @param image The image to process.
|
||||
*/
|
||||
void process(Mat image);
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj.vision;
|
||||
|
||||
import edu.wpi.first.cameraserver.CameraServerSharedStore;
|
||||
import edu.wpi.first.cscore.CvSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
* A vision runner is a convenient wrapper object to make it easy to run vision pipelines from robot
|
||||
* code. The easiest way to use this is to run it in a {@link VisionThread} and use the listener to
|
||||
* take snapshots of the pipeline's outputs.
|
||||
*
|
||||
* @see VisionPipeline
|
||||
* @see VisionThread
|
||||
* @see <a href="package-summary.html">vision</a>
|
||||
* @deprecated Replaced with edu.wpi.first.vision.VisionRunner
|
||||
*/
|
||||
@Deprecated
|
||||
public class VisionRunner<P extends VisionPipeline> {
|
||||
private final CvSink m_cvSink = new CvSink("VisionRunner CvSink");
|
||||
private final P m_pipeline;
|
||||
private final Mat m_image = new Mat();
|
||||
private final Listener<? super P> m_listener;
|
||||
private volatile boolean m_enabled = true;
|
||||
|
||||
/**
|
||||
* Listener interface for a callback that should run after a pipeline has processed its input.
|
||||
*
|
||||
* @param <P> the type of the pipeline this listener is for
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Listener<P extends VisionPipeline> {
|
||||
/**
|
||||
* Called when the pipeline has run. This shouldn't take much time to run because it will delay
|
||||
* later calls to the pipeline's {@link VisionPipeline#process process} method. Copying the
|
||||
* outputs and code that uses the copies should be <i>synchronized</i> on the same mutex to
|
||||
* prevent multiple threads from reading and writing to the same memory at the same time.
|
||||
*
|
||||
* @param pipeline the vision pipeline that ran
|
||||
*/
|
||||
void copyPipelineOutputs(P pipeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new vision runner. It will take images from the {@code videoSource}, send them to the
|
||||
* {@code pipeline}, and call the {@code listener} when the pipeline has finished to alert user
|
||||
* code when it is safe to access the pipeline's outputs.
|
||||
*
|
||||
* @param videoSource the video source to use to supply images for the pipeline
|
||||
* @param pipeline the vision pipeline to run
|
||||
* @param listener a function to call after the pipeline has finished running
|
||||
*/
|
||||
public VisionRunner(VideoSource videoSource, P pipeline, Listener<? super P> listener) {
|
||||
this.m_pipeline = pipeline;
|
||||
this.m_listener = listener;
|
||||
m_cvSink.setSource(videoSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the pipeline one time, giving it the next image from the video source specified in the
|
||||
* constructor. This will block until the source either has an image or throws an error. If the
|
||||
* source successfully supplied a frame, the pipeline's image input will be set, the pipeline will
|
||||
* run, and the listener specified in the constructor will be called to notify it that the
|
||||
* pipeline ran.
|
||||
*
|
||||
* <p>This method is exposed to allow teams to add additional functionality or have their own ways
|
||||
* to run the pipeline. Most teams, however, should just use {@link #runForever} in its own thread
|
||||
* using a {@link VisionThread}.
|
||||
*/
|
||||
public void runOnce() {
|
||||
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
||||
|
||||
if (id != null && Thread.currentThread().getId() == id) {
|
||||
throw new IllegalStateException(
|
||||
"VisionRunner.runOnce() cannot be called from the main robot thread");
|
||||
}
|
||||
runOnceInternal();
|
||||
}
|
||||
|
||||
private void runOnceInternal() {
|
||||
long frameTime = m_cvSink.grabFrame(m_image);
|
||||
if (frameTime == 0) {
|
||||
// There was an error, report it
|
||||
String error = m_cvSink.getError();
|
||||
CameraServerSharedStore.getCameraServerShared().reportDriverStationError(error);
|
||||
} else {
|
||||
// No errors, process the image
|
||||
m_pipeline.process(m_image);
|
||||
m_listener.copyPipelineOutputs(m_pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method that calls {@link #runOnce()} in an infinite loop. This must be run in a
|
||||
* dedicated thread, and cannot be used in the main robot thread because it will freeze the robot
|
||||
* program.
|
||||
*
|
||||
* <p><strong>Do not call this method directly from the main thread.</strong>
|
||||
*
|
||||
* @throws IllegalStateException if this is called from the main robot thread
|
||||
* @see VisionThread
|
||||
*/
|
||||
public void runForever() {
|
||||
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
||||
|
||||
if (id != null && Thread.currentThread().getId() == id) {
|
||||
throw new IllegalStateException(
|
||||
"VisionRunner.runForever() cannot be called from the main robot thread");
|
||||
}
|
||||
while (m_enabled && !Thread.interrupted()) {
|
||||
runOnceInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop a RunForever() loop. */
|
||||
public void stop() {
|
||||
m_enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.wpilibj.vision;
|
||||
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
|
||||
/**
|
||||
* A vision thread is a special thread that runs a vision pipeline. It is a <i>daemon</i> thread; it
|
||||
* does not prevent the program from exiting when all other non-daemon threads have finished
|
||||
* running.
|
||||
*
|
||||
* @see VisionPipeline
|
||||
* @see VisionRunner
|
||||
* @see Thread#setDaemon(boolean)
|
||||
* @deprecated Replaced with edu.wpi.first.vision.VisionThread
|
||||
*/
|
||||
@Deprecated
|
||||
public class VisionThread extends Thread {
|
||||
/**
|
||||
* Creates a vision thread that continuously runs a {@link VisionPipeline}.
|
||||
*
|
||||
* @param visionRunner the runner for a vision pipeline
|
||||
*/
|
||||
public VisionThread(VisionRunner<?> visionRunner) {
|
||||
super(visionRunner::runForever, "WPILib Vision Thread");
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new vision thread that continuously runs the given vision pipeline. This is
|
||||
* equivalent to {@code new VisionThread(new VisionRunner<>(videoSource, pipeline, listener))}.
|
||||
*
|
||||
* @param videoSource the source for images the pipeline should process
|
||||
* @param pipeline the pipeline to run
|
||||
* @param listener the listener to copy outputs from the pipeline after it runs
|
||||
* @param <P> the type of the pipeline
|
||||
*/
|
||||
public <P extends VisionPipeline> VisionThread(
|
||||
VideoSource videoSource, P pipeline, VisionRunner.Listener<? super P> listener) {
|
||||
this(new VisionRunner<>(videoSource, pipeline, listener));
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
/**
|
||||
* Classes in the {@code edu.wpi.first.wpilibj.vision} package are designed to simplify using OpenCV
|
||||
* vision processing code from a robot program.
|
||||
*
|
||||
* <p>An example use case for grabbing a yellow tote from 2015 in autonomous: <br>
|
||||
*
|
||||
* <pre><code>
|
||||
* public class Robot extends IterativeRobot
|
||||
* implements VisionRunner.Listener<MyFindTotePipeline> {
|
||||
*
|
||||
* // A USB camera connected to the roboRIO.
|
||||
* private {@link edu.wpi.first.cscore.VideoSource VideoSource} usbCamera;
|
||||
*
|
||||
* // A vision pipeline. This could be handwritten or generated by GRIP.
|
||||
* // This has to implement {@link edu.wpi.first.wpilibj.vision.VisionPipeline}.
|
||||
* // For this example, assume that it's perfect and will always see the tote.
|
||||
* private MyFindTotePipeline findTotePipeline;
|
||||
* private {@link edu.wpi.first.wpilibj.vision.VisionThread} findToteThread;
|
||||
*
|
||||
* // The object to synchronize on to make sure the vision thread doesn't
|
||||
* // write to variables the main thread is using.
|
||||
* private final Object visionLock = new Object();
|
||||
*
|
||||
* // The pipeline outputs we want
|
||||
* private boolean pipelineRan = false; // lets us know when the pipeline has actually run
|
||||
* private double angleToTote = 0;
|
||||
* private double distanceToTote = 0;
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void {@link edu.wpi.first.wpilibj.vision.VisionRunner.Listener#copyPipelineOutputs
|
||||
* copyPipelineOutputs(MyFindTotePipeline pipeline)} {
|
||||
* synchronized (visionLock) {
|
||||
* // Take a snapshot of the pipeline's output because
|
||||
* // it may have changed the next time this method is called!
|
||||
* this.pipelineRan = true;
|
||||
* this.angleToTote = pipeline.getAngleToTote();
|
||||
* this.distanceToTote = pipeline.getDistanceToTote();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void robotInit() {
|
||||
* usbCamera = CameraServer.startAutomaticCapture(0);
|
||||
* findTotePipeline = new MyFindTotePipeline();
|
||||
* findToteThread = new VisionThread(usbCamera, findTotePipeline, this);
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void autonomousInit() {
|
||||
* findToteThread.start();
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void autonomousPeriodic() {
|
||||
* double angle;
|
||||
* double distance;
|
||||
* synchronized (visionLock) {
|
||||
* if (!pipelineRan) {
|
||||
* // Wait until the pipeline has run
|
||||
* return;
|
||||
* }
|
||||
* // Copy the outputs to make sure they're all from the same run
|
||||
* angle = this.angleToTote;
|
||||
* distance = this.distanceToTote;
|
||||
* }
|
||||
* if (!aimedAtTote()) {
|
||||
* turnToAngle(angle);
|
||||
* } else if (!droveToTote()) {
|
||||
* driveDistance(distance);
|
||||
* } else if (!grabbedTote()) {
|
||||
* grabTote();
|
||||
* } else {
|
||||
* // Tote was grabbed and we're done!
|
||||
* return;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </code></pre>
|
||||
*/
|
||||
@java.lang.Deprecated
|
||||
package edu.wpi.first.wpilibj.vision;
|
||||
Reference in New Issue
Block a user