mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-27 02:01:42 +00:00
SCRIPT Move java files
This commit is contained in:
committed by
Peter Johnson
parent
7ca1be9bae
commit
c350c5f112
@@ -0,0 +1,285 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.hal.SimDouble;
|
||||
import edu.wpi.first.hal.SimEnum;
|
||||
import edu.wpi.first.networktables.DoublePublisher;
|
||||
import edu.wpi.first.networktables.DoubleTopic;
|
||||
import edu.wpi.first.networktables.NTSendable;
|
||||
import edu.wpi.first.networktables.NTSendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/** ADXL345 I2C Accelerometer. */
|
||||
@SuppressWarnings("TypeName")
|
||||
public class ADXL345_I2C implements NTSendable, AutoCloseable {
|
||||
/** Default I2C device address. */
|
||||
public static final byte kAddress = 0x1D;
|
||||
|
||||
private static final byte kPowerCtlRegister = 0x2D;
|
||||
private static final byte kDataFormatRegister = 0x31;
|
||||
private static final byte kDataRegister = 0x32;
|
||||
private static final double kGsPerLSB = 0.00390625;
|
||||
// private static final byte kPowerCtl_Link = 0x20;
|
||||
// private static final byte kPowerCtl_AutoSleep = 0x10;
|
||||
private static final byte kPowerCtl_Measure = 0x08;
|
||||
// private static final byte kPowerCtl_Sleep = 0x04;
|
||||
|
||||
// private static final byte kDataFormat_SelfTest = (byte) 0x80;
|
||||
// private static final byte kDataFormat_SPI = 0x40;
|
||||
// private static final byte kDataFormat_IntInvert = 0x20;
|
||||
private static final byte kDataFormat_FullRes = 0x08;
|
||||
|
||||
// private static final byte kDataFormat_Justify = 0x04;
|
||||
|
||||
/** Accelerometer range. */
|
||||
public enum Range {
|
||||
/** 2 Gs max. */
|
||||
k2G,
|
||||
/** 4 Gs max. */
|
||||
k4G,
|
||||
/** 8 Gs max. */
|
||||
k8G,
|
||||
/** 16 Gs max. */
|
||||
k16G
|
||||
}
|
||||
|
||||
/** Accelerometer axes. */
|
||||
public enum Axes {
|
||||
/** X axis. */
|
||||
kX((byte) 0x00),
|
||||
/** Y axis. */
|
||||
kY((byte) 0x02),
|
||||
/** Z axis. */
|
||||
kZ((byte) 0x04);
|
||||
|
||||
/** The integer value representing this enumeration. */
|
||||
public final byte value;
|
||||
|
||||
Axes(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Container type for accelerations from all axes. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public static class AllAxes {
|
||||
/** Acceleration along the X axis in g-forces. */
|
||||
public double XAxis;
|
||||
|
||||
/** Acceleration along the Y axis in g-forces. */
|
||||
public double YAxis;
|
||||
|
||||
/** Acceleration along the Z axis in g-forces. */
|
||||
public double ZAxis;
|
||||
|
||||
/** Default constructor. */
|
||||
public AllAxes() {}
|
||||
}
|
||||
|
||||
private I2C m_i2c;
|
||||
|
||||
private SimDevice m_simDevice;
|
||||
private SimEnum m_simRange;
|
||||
private SimDouble m_simX;
|
||||
private SimDouble m_simY;
|
||||
private SimDouble m_simZ;
|
||||
|
||||
/**
|
||||
* Constructs the ADXL345 Accelerometer with I2C address 0x1D.
|
||||
*
|
||||
* @param port The I2C port the accelerometer is attached to
|
||||
* @param range The range (+ or -) that the accelerometer will measure.
|
||||
*/
|
||||
public ADXL345_I2C(I2C.Port port, Range range) {
|
||||
this(port, range, kAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the ADXL345 Accelerometer over I2C.
|
||||
*
|
||||
* @param port The I2C port the accelerometer is attached to
|
||||
* @param range The range (+ or -) that the accelerometer will measure.
|
||||
* @param deviceAddress I2C address of the accelerometer (0x1D or 0x53)
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public ADXL345_I2C(I2C.Port port, Range range, int deviceAddress) {
|
||||
m_i2c = new I2C(port, deviceAddress);
|
||||
|
||||
// simulation
|
||||
m_simDevice = SimDevice.create("Accel:ADXL345_I2C", port.value, deviceAddress);
|
||||
if (m_simDevice != null) {
|
||||
m_simRange =
|
||||
m_simDevice.createEnumDouble(
|
||||
"range",
|
||||
SimDevice.Direction.kOutput,
|
||||
new String[] {"2G", "4G", "8G", "16G"},
|
||||
new double[] {2.0, 4.0, 8.0, 16.0},
|
||||
0);
|
||||
m_simX = m_simDevice.createDouble("x", SimDevice.Direction.kInput, 0.0);
|
||||
m_simY = m_simDevice.createDouble("y", SimDevice.Direction.kInput, 0.0);
|
||||
m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.kInput, 0.0);
|
||||
}
|
||||
|
||||
// Turn on the measurements
|
||||
m_i2c.write(kPowerCtlRegister, kPowerCtl_Measure);
|
||||
|
||||
setRange(range);
|
||||
|
||||
HAL.reportUsage("I2C[" + port.value + "][" + deviceAddress + "]", "ADXL345");
|
||||
SendableRegistry.add(this, "ADXL345_I2C", port.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the I2C port.
|
||||
*
|
||||
* @return The I2C port.
|
||||
*/
|
||||
public int getPort() {
|
||||
return m_i2c.getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the I2C device address.
|
||||
*
|
||||
* @return The I2C device address.
|
||||
*/
|
||||
public int getDeviceAddress() {
|
||||
return m_i2c.getDeviceAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
if (m_i2c != null) {
|
||||
m_i2c.close();
|
||||
m_i2c = null;
|
||||
}
|
||||
if (m_simDevice != null) {
|
||||
m_simDevice.close();
|
||||
m_simDevice = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the measuring range of the accelerometer.
|
||||
*
|
||||
* @param range The maximum acceleration, positive or negative, that the accelerometer will
|
||||
* measure.
|
||||
*/
|
||||
public final void setRange(Range range) {
|
||||
final byte value =
|
||||
switch (range) {
|
||||
case k2G -> 0;
|
||||
case k4G -> 1;
|
||||
case k8G -> 2;
|
||||
case k16G -> 3;
|
||||
};
|
||||
|
||||
// Specify the data format to read
|
||||
m_i2c.write(kDataFormatRegister, kDataFormat_FullRes | value);
|
||||
|
||||
if (m_simRange != null) {
|
||||
m_simRange.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the acceleration along the X axis in g-forces.
|
||||
*
|
||||
* @return The acceleration along the X axis in g-forces.
|
||||
*/
|
||||
public double getX() {
|
||||
return getAcceleration(Axes.kX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the acceleration along the Y axis in g-forces.
|
||||
*
|
||||
* @return The acceleration along the Y axis in g-forces.
|
||||
*/
|
||||
public double getY() {
|
||||
return getAcceleration(Axes.kY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the acceleration along the Z axis in g-forces.
|
||||
*
|
||||
* @return The acceleration along the Z axis in g-forces.
|
||||
*/
|
||||
public double getZ() {
|
||||
return getAcceleration(Axes.kZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the acceleration of one axis in Gs.
|
||||
*
|
||||
* @param axis The axis to read from.
|
||||
* @return Acceleration of the ADXL345 in Gs.
|
||||
*/
|
||||
public double getAcceleration(Axes axis) {
|
||||
if (axis == Axes.kX && m_simX != null) {
|
||||
return m_simX.get();
|
||||
}
|
||||
if (axis == Axes.kY && m_simY != null) {
|
||||
return m_simY.get();
|
||||
}
|
||||
if (axis == Axes.kZ && m_simZ != null) {
|
||||
return m_simZ.get();
|
||||
}
|
||||
ByteBuffer rawAccel = ByteBuffer.allocate(2);
|
||||
m_i2c.read(kDataRegister + axis.value, 2, rawAccel);
|
||||
|
||||
// Sensor is little endian... swap bytes
|
||||
rawAccel.order(ByteOrder.LITTLE_ENDIAN);
|
||||
return rawAccel.getShort(0) * kGsPerLSB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the acceleration of all axes in Gs.
|
||||
*
|
||||
* @return An object containing the acceleration measured on each axis of the ADXL345 in Gs.
|
||||
*/
|
||||
public AllAxes getAccelerations() {
|
||||
AllAxes data = new AllAxes();
|
||||
if (m_simX != null && m_simY != null && m_simZ != null) {
|
||||
data.XAxis = m_simX.get();
|
||||
data.YAxis = m_simY.get();
|
||||
data.ZAxis = m_simZ.get();
|
||||
return data;
|
||||
}
|
||||
ByteBuffer rawData = ByteBuffer.allocate(6);
|
||||
m_i2c.read(kDataRegister, 6, rawData);
|
||||
|
||||
// Sensor is little endian... swap bytes
|
||||
rawData.order(ByteOrder.LITTLE_ENDIAN);
|
||||
data.XAxis = rawData.getShort(0) * kGsPerLSB;
|
||||
data.YAxis = rawData.getShort(2) * kGsPerLSB;
|
||||
data.ZAxis = rawData.getShort(4) * kGsPerLSB;
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(NTSendableBuilder builder) {
|
||||
builder.setSmartDashboardType("3AxisAccelerometer");
|
||||
DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish();
|
||||
DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish();
|
||||
DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish();
|
||||
builder.addCloseable(pubX);
|
||||
builder.addCloseable(pubY);
|
||||
builder.addCloseable(pubZ);
|
||||
builder.setUpdateTable(
|
||||
() -> {
|
||||
AllAxes data = getAccelerations();
|
||||
pubX.set(data.XAxis);
|
||||
pubY.set(data.YAxis);
|
||||
pubZ.set(data.ZAxis);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Handle operation of an analog accelerometer. The accelerometer reads acceleration directly
|
||||
* through the sensor. Many sensors have multiple axis and can be treated as multiple devices. Each
|
||||
* is calibrated by finding the center value over a period of time.
|
||||
*/
|
||||
public class AnalogAccelerometer implements Sendable, AutoCloseable {
|
||||
private AnalogInput m_analogChannel;
|
||||
private double m_voltsPerG = 1.0;
|
||||
private double m_zeroGVoltage = 2.5;
|
||||
private final boolean m_allocatedChannel;
|
||||
|
||||
/** Common initialization. */
|
||||
private void initAccelerometer() {
|
||||
HAL.reportUsage("IO", m_analogChannel.getChannel(), "Accelerometer");
|
||||
SendableRegistry.add(this, "Accelerometer", m_analogChannel.getChannel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of an accelerometer.
|
||||
*
|
||||
* <p>The constructor allocates desired analog channel.
|
||||
*
|
||||
* @param channel The channel number for the analog input the accelerometer is connected to
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogAccelerometer(final int channel) {
|
||||
this(new AnalogInput(channel), true);
|
||||
SendableRegistry.addChild(this, m_analogChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of Accelerometer from an existing AnalogChannel. Make a new instance of
|
||||
* accelerometer given an AnalogChannel. This is particularly useful if the port is going to be
|
||||
* read as an analog channel as well as through the Accelerometer class.
|
||||
*
|
||||
* @param channel The existing AnalogInput object for the analog input the accelerometer is
|
||||
* connected to
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogAccelerometer(final AnalogInput channel) {
|
||||
this(channel, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
private AnalogAccelerometer(final AnalogInput channel, final boolean allocatedChannel) {
|
||||
requireNonNullParam(channel, "channel", "AnalogAccelerometer");
|
||||
m_allocatedChannel = allocatedChannel;
|
||||
m_analogChannel = channel;
|
||||
initAccelerometer();
|
||||
}
|
||||
|
||||
/** Delete the analog components used for the accelerometer. */
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
if (m_analogChannel != null && m_allocatedChannel) {
|
||||
m_analogChannel.close();
|
||||
}
|
||||
m_analogChannel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the acceleration in Gs.
|
||||
*
|
||||
* <p>The acceleration is returned units of Gs.
|
||||
*
|
||||
* @return The current acceleration of the sensor in Gs.
|
||||
*/
|
||||
public double getAcceleration() {
|
||||
if (m_analogChannel == null) {
|
||||
return 0.0;
|
||||
}
|
||||
return (m_analogChannel.getVoltage() - m_zeroGVoltage) / m_voltsPerG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the accelerometer sensitivity.
|
||||
*
|
||||
* <p>This sets the sensitivity of the accelerometer used for calculating the acceleration. The
|
||||
* sensitivity varies by accelerometer model.
|
||||
*
|
||||
* @param sensitivity The sensitivity of accelerometer in Volts per G.
|
||||
*/
|
||||
public void setSensitivity(double sensitivity) {
|
||||
m_voltsPerG = sensitivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the voltage that corresponds to 0 G.
|
||||
*
|
||||
* <p>The zero G voltage varies by accelerometer model.
|
||||
*
|
||||
* @param zero The zero G voltage.
|
||||
*/
|
||||
public void setZero(double zero) {
|
||||
m_zeroGVoltage = zero;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Accelerometer");
|
||||
builder.addDoubleProperty("Value", this::getAcceleration, null);
|
||||
}
|
||||
}
|
||||
194
wpilibj/src/main/java/org/wpilib/hardware/bus/CAN.java
Normal file
194
wpilibj/src/main/java/org/wpilib/hardware/bus/CAN.java
Normal file
@@ -0,0 +1,194 @@
|
||||
// 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.hal.CANAPIJNI;
|
||||
import edu.wpi.first.hal.CANAPITypes;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.can.CANReceiveMessage;
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* High level class for interfacing with CAN devices conforming to the standard CAN spec.
|
||||
*
|
||||
* <p>No packets that can be sent gets blocked by the RoboRIO, so all methods work identically in
|
||||
* all robot modes.
|
||||
*
|
||||
* <p>All methods are thread safe, however the CANData object passed into the read methods and the
|
||||
* byte[] passed into the write methods need to not be modified for the duration of their respective
|
||||
* calls.
|
||||
*/
|
||||
public class CAN implements Closeable {
|
||||
/** Team manufacturer. */
|
||||
public static final int kTeamManufacturer = CANAPITypes.CANManufacturer.kTeamUse.id;
|
||||
|
||||
/** Team device type. */
|
||||
public static final int kTeamDeviceType = CANAPITypes.CANDeviceType.kMiscellaneous.id;
|
||||
|
||||
private final int m_handle;
|
||||
|
||||
/**
|
||||
* Create a new CAN communication interface with the specific device ID. This uses the team
|
||||
* manufacturer and device types. The device ID is 6 bits (0-63).
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param deviceId The device id
|
||||
*/
|
||||
public CAN(int busId, int deviceId) {
|
||||
this(busId, deviceId, kTeamManufacturer, kTeamDeviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CAN communication interface with a specific device ID, manufacturer and device
|
||||
* type. The device ID is 6 bits, the manufacturer is 8 bits, and the device type is 5 bits.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param deviceId The device ID
|
||||
* @param deviceManufacturer The device manufacturer
|
||||
* @param deviceType The device type
|
||||
*/
|
||||
public CAN(int busId, int deviceId, int deviceManufacturer, int deviceType) {
|
||||
m_handle = CANAPIJNI.initializeCAN(busId, deviceManufacturer, deviceId, deviceType);
|
||||
HAL.reportUsage("CAN[" + deviceType + "][" + deviceManufacturer + "][" + deviceId + "]", "");
|
||||
}
|
||||
|
||||
/** Closes the CAN communication. */
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_handle != 0) {
|
||||
CANAPIJNI.cleanCAN(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a packet to the CAN device with a specific ID. This ID is 10 bits.
|
||||
*
|
||||
* @param apiId The API ID to write.
|
||||
* @param data The data to write
|
||||
* @param dataLength The data length
|
||||
* @param flags The flags
|
||||
*/
|
||||
public void writePacket(int apiId, byte[] data, int dataLength, int flags) {
|
||||
CANAPIJNI.writeCANPacket(m_handle, apiId, data, dataLength, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a repeating packet to the CAN device with a specific ID. This ID is 10 bits. The RoboRIO
|
||||
* will automatically repeat the packet at the specified interval
|
||||
*
|
||||
* @param apiId The API ID to write.
|
||||
* @param data The data to write
|
||||
* @param dataLength The data length
|
||||
* @param flags The flags
|
||||
* @param repeatMs The period to repeat the packet at.
|
||||
*/
|
||||
public void writePacketRepeating(
|
||||
int apiId, byte[] data, int dataLength, int flags, int repeatMs) {
|
||||
CANAPIJNI.writeCANPacketRepeating(m_handle, apiId, data, dataLength, flags, repeatMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an RTR frame to the CAN device with a specific ID. This ID is 10 bits. The length by spec
|
||||
* must match what is returned by the responding device
|
||||
*
|
||||
* @param apiId The API ID to write.
|
||||
* @param data The data to write
|
||||
* @param dataLength The data length
|
||||
* @param flags The flags
|
||||
*/
|
||||
public void writeRTRFrame(int apiId, byte[] data, int dataLength, int flags) {
|
||||
CANAPIJNI.writeCANRTRFrame(m_handle, apiId, data, dataLength, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a packet to the CAN device with a specific ID. This ID is 10 bits.
|
||||
*
|
||||
* @param apiId The API ID to write.
|
||||
* @param data The data to write
|
||||
* @param dataLength The data length
|
||||
* @param flags The flags
|
||||
* @return TODO
|
||||
*/
|
||||
public int writePacketNoThrow(int apiId, byte[] data, int dataLength, int flags) {
|
||||
return CANAPIJNI.writeCANPacketNoThrow(m_handle, apiId, data, dataLength, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a repeating packet to the CAN device with a specific ID. This ID is 10 bits. The RoboRIO
|
||||
* will automatically repeat the packet at the specified interval
|
||||
*
|
||||
* @param apiId The API ID to write.
|
||||
* @param data The data to write
|
||||
* @param dataLength The data length
|
||||
* @param flags The flags
|
||||
* @param repeatMs The period to repeat the packet at.
|
||||
* @return TODO
|
||||
*/
|
||||
public int writePacketRepeatingNoThrow(
|
||||
int apiId, byte[] data, int dataLength, int flags, int repeatMs) {
|
||||
return CANAPIJNI.writeCANPacketRepeatingNoThrow(
|
||||
m_handle, apiId, data, dataLength, flags, repeatMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an RTR frame to the CAN device with a specific ID. This ID is 10 bits. The length by spec
|
||||
* must match what is returned by the responding device
|
||||
*
|
||||
* @param apiId The API ID to write.
|
||||
* @param data The data to write
|
||||
* @param dataLength The data length
|
||||
* @param flags The flags
|
||||
* @return TODO
|
||||
*/
|
||||
public int writeRTRFrameNoThrow(int apiId, byte[] data, int dataLength, int flags) {
|
||||
return CANAPIJNI.writeCANRTRFrameNoThrow(m_handle, apiId, data, dataLength, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a repeating packet with a specific ID. This ID is 10 bits.
|
||||
*
|
||||
* @param apiId The API ID to stop repeating
|
||||
*/
|
||||
public void stopPacketRepeating(int apiId) {
|
||||
CANAPIJNI.stopCANPacketRepeating(m_handle, apiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a new CAN packet. This will only return properly once per packet received. Multiple calls
|
||||
* without receiving another packet will return false.
|
||||
*
|
||||
* @param apiId The API ID to read.
|
||||
* @param data Storage for the received data.
|
||||
* @return True if the data is valid, otherwise false.
|
||||
*/
|
||||
public boolean readPacketNew(int apiId, CANReceiveMessage data) {
|
||||
return CANAPIJNI.readCANPacketNew(m_handle, apiId, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a CAN packet. This will continuously return the last packet received, without accounting
|
||||
* for packet age.
|
||||
*
|
||||
* @param apiId The API ID to read.
|
||||
* @param data Storage for the received data.
|
||||
* @return True if the data is valid, otherwise false.
|
||||
*/
|
||||
public boolean readPacketLatest(int apiId, CANReceiveMessage data) {
|
||||
return CANAPIJNI.readCANPacketLatest(m_handle, apiId, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a CAN packet. This will return the last packet received until the packet is older than the
|
||||
* requested timeout. Then it will return false.
|
||||
*
|
||||
* @param apiId The API ID to read.
|
||||
* @param timeoutMs The timeout time for the packet
|
||||
* @param data Storage for the received data.
|
||||
* @return True if the data is valid, otherwise false.
|
||||
*/
|
||||
public boolean readPacketTimeout(int apiId, CANReceiveMessage data, int timeoutMs) {
|
||||
return CANAPIJNI.readCANPacketTimeout(m_handle, apiId, data, timeoutMs);
|
||||
}
|
||||
}
|
||||
390
wpilibj/src/main/java/org/wpilib/hardware/bus/I2C.java
Normal file
390
wpilibj/src/main/java/org/wpilib/hardware/bus/I2C.java
Normal file
@@ -0,0 +1,390 @@
|
||||
// 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 static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.I2CJNI;
|
||||
import edu.wpi.first.hal.util.BoundaryException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* I2C bus interface class.
|
||||
*
|
||||
* <p>This class is intended to be used by sensor (and other I2C device) drivers. It probably should
|
||||
* not be used directly.
|
||||
*/
|
||||
public class I2C implements AutoCloseable {
|
||||
/** I2C connection ports. */
|
||||
public enum Port {
|
||||
/** I2C Port 0. */
|
||||
kPort0(0),
|
||||
/** I2C Port 1. */
|
||||
kPort1(1);
|
||||
|
||||
/** Port value. */
|
||||
public final int value;
|
||||
|
||||
Port(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private final int m_port;
|
||||
private final int m_deviceAddress;
|
||||
private ByteBuffer m_readDataToSendBuffer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param port The I2C port the device is connected to.
|
||||
* @param deviceAddress The address of the device on the I2C bus.
|
||||
*/
|
||||
public I2C(Port port, int deviceAddress) {
|
||||
m_port = port.value;
|
||||
m_deviceAddress = deviceAddress;
|
||||
|
||||
I2CJNI.i2CInitialize((byte) port.value);
|
||||
|
||||
HAL.reportUsage("I2C[" + port.value + "][" + deviceAddress + "]", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns I2C port.
|
||||
*
|
||||
* @return I2C port.
|
||||
*/
|
||||
public int getPort() {
|
||||
return m_port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns I2C device address.
|
||||
*
|
||||
* @return I2C device address.
|
||||
*/
|
||||
public int getDeviceAddress() {
|
||||
return m_deviceAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
I2CJNI.i2CClose(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic transaction.
|
||||
*
|
||||
* <p>This is a lower-level interface to the I2C hardware giving you more control over each
|
||||
* transaction. If you intend to write multiple bytes in the same transaction and do not plan to
|
||||
* receive anything back, use writeBulk() instead. Calling this with a receiveSize of 0 will
|
||||
* result in an error.
|
||||
*
|
||||
* @param dataToSend Buffer of data to send as part of the transaction.
|
||||
* @param sendSize Number of bytes to send as part of the transaction.
|
||||
* @param dataReceived Buffer to read data into.
|
||||
* @param receiveSize Number of bytes to read from the device.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public synchronized boolean transaction(
|
||||
byte[] dataToSend, int sendSize, byte[] dataReceived, int receiveSize) {
|
||||
if (dataToSend.length < sendSize) {
|
||||
throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize);
|
||||
}
|
||||
if (dataReceived.length < receiveSize) {
|
||||
throw new IllegalArgumentException(
|
||||
"dataReceived is too small, must be at least " + receiveSize);
|
||||
}
|
||||
return I2CJNI.i2CTransactionB(
|
||||
m_port,
|
||||
(byte) m_deviceAddress,
|
||||
dataToSend,
|
||||
(byte) sendSize,
|
||||
dataReceived,
|
||||
(byte) receiveSize)
|
||||
< 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic transaction.
|
||||
*
|
||||
* <p>This is a lower-level interface to the I2C hardware giving you more control over each
|
||||
* transaction.
|
||||
*
|
||||
* @param dataToSend Buffer of data to send as part of the transaction.
|
||||
* @param sendSize Number of bytes to send as part of the transaction.
|
||||
* @param dataReceived Buffer to read data into.
|
||||
* @param receiveSize Number of bytes to read from the device.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public synchronized boolean transaction(
|
||||
ByteBuffer dataToSend, int sendSize, ByteBuffer dataReceived, int receiveSize) {
|
||||
if (dataToSend.hasArray() && dataReceived.hasArray()) {
|
||||
return transaction(dataToSend.array(), sendSize, dataReceived.array(), receiveSize);
|
||||
}
|
||||
if (!dataToSend.isDirect()) {
|
||||
throw new IllegalArgumentException("dataToSend must be a direct buffer");
|
||||
}
|
||||
if (dataToSend.capacity() < sendSize) {
|
||||
throw new IllegalArgumentException("dataToSend is too small, must be at least " + sendSize);
|
||||
}
|
||||
if (!dataReceived.isDirect()) {
|
||||
throw new IllegalArgumentException("dataReceived must be a direct buffer");
|
||||
}
|
||||
if (dataReceived.capacity() < receiveSize) {
|
||||
throw new IllegalArgumentException(
|
||||
"dataReceived is too small, must be at least " + receiveSize);
|
||||
}
|
||||
|
||||
return I2CJNI.i2CTransaction(
|
||||
m_port,
|
||||
(byte) m_deviceAddress,
|
||||
dataToSend,
|
||||
(byte) sendSize,
|
||||
dataReceived,
|
||||
(byte) receiveSize)
|
||||
< 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to address a device on the I2C bus.
|
||||
*
|
||||
* <p>This allows you to figure out if there is a device on the I2C bus that responds to the
|
||||
* address specified in the constructor.
|
||||
*
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public boolean addressOnly() {
|
||||
return transaction(new byte[0], (byte) 0, new byte[0], (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a write transaction with the device.
|
||||
*
|
||||
* <p>Write a single byte to a register on a device and wait until the transaction is complete.
|
||||
*
|
||||
* @param registerAddress The address of the register on the device to be written.
|
||||
* @param data The byte to write to the register on the device.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public synchronized boolean write(int registerAddress, int data) {
|
||||
byte[] buffer = new byte[2];
|
||||
buffer[0] = (byte) registerAddress;
|
||||
buffer[1] = (byte) data;
|
||||
return I2CJNI.i2CWriteB(m_port, (byte) m_deviceAddress, buffer, (byte) buffer.length) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a write transaction with the device.
|
||||
*
|
||||
* <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
|
||||
*
|
||||
* @param data The data to write to the device.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public synchronized boolean writeBulk(byte[] data) {
|
||||
return writeBulk(data, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a write transaction with the device.
|
||||
*
|
||||
* <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
|
||||
*
|
||||
* @param data The data to write to the device.
|
||||
* @param size The number of data bytes to write.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public synchronized boolean writeBulk(byte[] data, int size) {
|
||||
if (data.length < size) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + size);
|
||||
}
|
||||
return I2CJNI.i2CWriteB(m_port, (byte) m_deviceAddress, data, (byte) size) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a write transaction with the device.
|
||||
*
|
||||
* <p>Write multiple bytes to a register on a device and wait until the transaction is complete.
|
||||
*
|
||||
* @param data The data to write to the device.
|
||||
* @param size The number of data bytes to write.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public synchronized boolean writeBulk(ByteBuffer data, int size) {
|
||||
if (data.hasArray()) {
|
||||
return writeBulk(data.array(), size);
|
||||
}
|
||||
if (!data.isDirect()) {
|
||||
throw new IllegalArgumentException("must be a direct buffer");
|
||||
}
|
||||
if (data.capacity() < size) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + size);
|
||||
}
|
||||
|
||||
return I2CJNI.i2CWrite(m_port, (byte) m_deviceAddress, data, (byte) size) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a read transaction with the device.
|
||||
*
|
||||
* <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer
|
||||
* internally allowing you to read consecutive registers on a device in a single transaction.
|
||||
*
|
||||
* @param registerAddress The register to read first in the transaction.
|
||||
* @param count The number of bytes to read in the transaction.
|
||||
* @param buffer A pointer to the array of bytes to store the data read from the device.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public boolean read(int registerAddress, int count, byte[] buffer) {
|
||||
requireNonNullParam(buffer, "buffer", "read");
|
||||
|
||||
if (count < 1) {
|
||||
throw new BoundaryException("Value must be at least 1, " + count + " given");
|
||||
}
|
||||
if (buffer.length < count) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + count);
|
||||
}
|
||||
|
||||
byte[] registerAddressArray = new byte[1];
|
||||
registerAddressArray[0] = (byte) registerAddress;
|
||||
|
||||
return transaction(registerAddressArray, registerAddressArray.length, buffer, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a read transaction with the device.
|
||||
*
|
||||
* <p>Read bytes from a device. Most I2C devices will auto-increment the register pointer
|
||||
* internally allowing you to read consecutive registers on a device in a single transaction.
|
||||
*
|
||||
* @param registerAddress The register to read first in the transaction.
|
||||
* @param count The number of bytes to read in the transaction.
|
||||
* @param buffer A buffer to store the data read from the device.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public boolean read(int registerAddress, int count, ByteBuffer buffer) {
|
||||
if (count < 1) {
|
||||
throw new BoundaryException("Value must be at least 1, " + count + " given");
|
||||
}
|
||||
|
||||
if (buffer.hasArray()) {
|
||||
return read(registerAddress, count, buffer.array());
|
||||
}
|
||||
|
||||
if (!buffer.isDirect()) {
|
||||
throw new IllegalArgumentException("must be a direct buffer");
|
||||
}
|
||||
if (buffer.capacity() < count) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + count);
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (m_readDataToSendBuffer == null) {
|
||||
m_readDataToSendBuffer = ByteBuffer.allocateDirect(1);
|
||||
}
|
||||
m_readDataToSendBuffer.put(0, (byte) registerAddress);
|
||||
|
||||
return transaction(m_readDataToSendBuffer, 1, buffer, count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a read only transaction with the device.
|
||||
*
|
||||
* <p>Read bytes from a device. This method does not write any data to prompt the device.
|
||||
*
|
||||
* @param buffer A pointer to the array of bytes to store the data read from the device.
|
||||
* @param count The number of bytes to read in the transaction.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public boolean readOnly(byte[] buffer, int count) {
|
||||
requireNonNullParam(buffer, "buffer", "readOnly");
|
||||
if (count < 1) {
|
||||
throw new BoundaryException("Value must be at least 1, " + count + " given");
|
||||
}
|
||||
if (buffer.length < count) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + count);
|
||||
}
|
||||
|
||||
return I2CJNI.i2CReadB(m_port, (byte) m_deviceAddress, buffer, (byte) count) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a read only transaction with the device.
|
||||
*
|
||||
* <p>Read bytes from a device. This method does not write any data to prompt the device.
|
||||
*
|
||||
* @param buffer A pointer to the array of bytes to store the data read from the device.
|
||||
* @param count The number of bytes to read in the transaction.
|
||||
* @return Transfer Aborted... false for success, true for aborted.
|
||||
*/
|
||||
public boolean readOnly(ByteBuffer buffer, int count) {
|
||||
if (count < 1) {
|
||||
throw new BoundaryException("Value must be at least 1, " + count + " given");
|
||||
}
|
||||
|
||||
if (buffer.hasArray()) {
|
||||
return readOnly(buffer.array(), count);
|
||||
}
|
||||
|
||||
if (!buffer.isDirect()) {
|
||||
throw new IllegalArgumentException("must be a direct buffer");
|
||||
}
|
||||
if (buffer.capacity() < count) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + count);
|
||||
}
|
||||
|
||||
return I2CJNI.i2CRead(m_port, (byte) m_deviceAddress, buffer, (byte) count) < 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a broadcast write to all devices on the I2C bus.
|
||||
*
|
||||
* <p>This is not currently implemented!
|
||||
*
|
||||
* @param registerAddress The register to write on all devices on the bus.
|
||||
* @param data The value to write to the devices.
|
||||
*/
|
||||
// public void broadcast(int registerAddress, int data) {
|
||||
// }
|
||||
|
||||
/**
|
||||
* Verify that a device's registers contain expected values.
|
||||
*
|
||||
* <p>Most devices will have a set of registers that contain a known value that can be used to
|
||||
* identify them. This allows an I2C device driver to easily verify that the device contains the
|
||||
* expected value.
|
||||
*
|
||||
* @param registerAddress The base register to start reading from the device.
|
||||
* @param count The size of the field to be verified.
|
||||
* @param expected A buffer containing the values expected from the device.
|
||||
* @return true if the sensor was verified to be connected
|
||||
* @pre The device must support and be configured to use register auto-increment.
|
||||
*/
|
||||
public boolean verifySensor(int registerAddress, int count, byte[] expected) {
|
||||
// TODO: Make use of all 7 read bytes
|
||||
byte[] dataToSend = new byte[1];
|
||||
|
||||
byte[] deviceData = new byte[4];
|
||||
for (int i = 0; i < count; i += 4) {
|
||||
int toRead = Math.min(count - i, 4);
|
||||
// Read the chunk of data. Return false if the sensor does not
|
||||
// respond.
|
||||
dataToSend[0] = (byte) (registerAddress + i);
|
||||
if (transaction(dataToSend, 1, deviceData, toRead)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (byte j = 0; j < toRead; j++) {
|
||||
if (deviceData[j] != expected[i + j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
358
wpilibj/src/main/java/org/wpilib/hardware/bus/SerialPort.java
Normal file
358
wpilibj/src/main/java/org/wpilib/hardware/bus/SerialPort.java
Normal file
@@ -0,0 +1,358 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.SerialPortJNI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Driver for the serial ports (USB, MXP, Onboard) on the roboRIO. */
|
||||
public class SerialPort implements AutoCloseable {
|
||||
private int m_portHandle;
|
||||
|
||||
/** Serial port. */
|
||||
public enum Port {
|
||||
/** Onboard serial port on the roboRIO. */
|
||||
kOnboard(0),
|
||||
/** MXP (roboRIO MXP) serial port. */
|
||||
kMXP(1),
|
||||
/** USB serial port (same as kUSB1). */
|
||||
kUSB(2),
|
||||
/** USB serial port 1. */
|
||||
kUSB1(2),
|
||||
/** USB serial port 2. */
|
||||
kUSB2(3);
|
||||
|
||||
/** Port value. */
|
||||
public final int value;
|
||||
|
||||
Port(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents the parity to use for serial communications. */
|
||||
public enum Parity {
|
||||
/** No parity. */
|
||||
kNone(0),
|
||||
/** Odd parity. */
|
||||
kOdd(1),
|
||||
/** Even parity. */
|
||||
kEven(2),
|
||||
/** Parity bit always on. */
|
||||
kMark(3),
|
||||
/** Parity bit always off. */
|
||||
kSpace(4);
|
||||
|
||||
/** Parity value. */
|
||||
public final int value;
|
||||
|
||||
Parity(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents the number of stop bits to use for Serial Communication. */
|
||||
public enum StopBits {
|
||||
/** One stop bit. */
|
||||
kOne(10),
|
||||
/** One and a half stop bits. */
|
||||
kOnePointFive(15),
|
||||
/** Two stop bits. */
|
||||
kTwo(20);
|
||||
|
||||
/** StopBits value. */
|
||||
public final int value;
|
||||
|
||||
StopBits(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents what type of flow control to use for serial communication. */
|
||||
public enum FlowControl {
|
||||
/** No flow control. */
|
||||
kNone(0),
|
||||
/** XON/XOFF flow control. */
|
||||
kXonXoff(1),
|
||||
/** RTS/CTS flow control. */
|
||||
kRtsCts(2),
|
||||
/** DTS/DSR flow control. */
|
||||
kDtsDsr(4);
|
||||
|
||||
/** FlowControl value. */
|
||||
public final int value;
|
||||
|
||||
FlowControl(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents which type of buffer mode to use when writing to a serial port. */
|
||||
public enum WriteBufferMode {
|
||||
/** Flush the buffer on each access. */
|
||||
kFlushOnAccess(1),
|
||||
/** Flush the buffer when it is full. */
|
||||
kFlushWhenFull(2);
|
||||
|
||||
/** WriteBufferMode value. */
|
||||
public final int value;
|
||||
|
||||
WriteBufferMode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a Serial Port class.
|
||||
*
|
||||
* @param baudRate The baud rate to configure the serial port.
|
||||
* @param port The Serial port 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.
|
||||
*/
|
||||
public SerialPort(
|
||||
final int baudRate, Port port, final int dataBits, Parity parity, StopBits stopBits) {
|
||||
m_portHandle = SerialPortJNI.serialInitializePort((byte) port.value);
|
||||
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.reportUsage("SerialPort", port.value, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a Serial Port class. Defaults to one stop bit.
|
||||
*
|
||||
* @param baudRate The baud rate to configure the serial port.
|
||||
* @param port The serial port 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.
|
||||
*/
|
||||
public SerialPort(final int baudRate, Port port, final int dataBits, Parity parity) {
|
||||
this(baudRate, port, dataBits, parity, StopBits.kOne);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a Serial Port class. Defaults to no parity and one stop bit.
|
||||
*
|
||||
* @param baudRate The baud rate to configure the serial port.
|
||||
* @param port The serial port to use.
|
||||
* @param dataBits The number of data bits per transfer. Valid values are between 5 and 8 bits.
|
||||
*/
|
||||
public SerialPort(final int baudRate, Port port, final int dataBits) {
|
||||
this(baudRate, port, dataBits, Parity.kNone, StopBits.kOne);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a Serial Port class. Defaults to 8 databits, no parity, and one stop bit.
|
||||
*
|
||||
* @param baudRate The baud rate to configure the serial port.
|
||||
* @param port The serial port to use.
|
||||
*/
|
||||
public SerialPort(final int baudRate, Port port) {
|
||||
this(baudRate, port, 8, Parity.kNone, StopBits.kOne);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SerialPortJNI.serialClose(m_portHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of flow control to enable on this port.
|
||||
*
|
||||
* <p>By default, flow control is disabled.
|
||||
*
|
||||
* @param flowControl the FlowControl m_value to use
|
||||
*/
|
||||
public void setFlowControl(FlowControl flowControl) {
|
||||
SerialPortJNI.serialSetFlowControl(m_portHandle, (byte) flowControl.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable termination and specify the termination character.
|
||||
*
|
||||
* <p>Termination is currently only implemented for receive. When the terminator is received, the
|
||||
* read() or readString() will return fewer bytes than requested, stopping after the terminator.
|
||||
*
|
||||
* @param terminator The character to use for termination.
|
||||
*/
|
||||
public void enableTermination(char terminator) {
|
||||
SerialPortJNI.serialEnableTermination(m_portHandle, terminator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable termination with the default terminator '\n'
|
||||
*
|
||||
* <p>Termination is currently only implemented for receive. When the terminator is received, the
|
||||
* read() or readString() will return fewer bytes than requested, stopping after the terminator.
|
||||
*
|
||||
* <p>The default terminator is '\n'
|
||||
*/
|
||||
public void enableTermination() {
|
||||
enableTermination('\n');
|
||||
}
|
||||
|
||||
/** Disable termination behavior. */
|
||||
public final void disableTermination() {
|
||||
SerialPortJNI.serialDisableTermination(m_portHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes currently available to read from the serial port.
|
||||
*
|
||||
* @return The number of bytes available to read.
|
||||
*/
|
||||
public int getBytesReceived() {
|
||||
return SerialPortJNI.serialGetBytesReceived(m_portHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string out of the buffer. Reads the entire contents of the buffer
|
||||
*
|
||||
* @return The read string
|
||||
*/
|
||||
public String readString() {
|
||||
return readString(getBytesReceived());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string out of the buffer. Reads the entire contents of the buffer
|
||||
*
|
||||
* @param count the number of characters to read into the string
|
||||
* @return The read string
|
||||
*/
|
||||
public String readString(int count) {
|
||||
byte[] out = read(count);
|
||||
return new String(out, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read raw bytes out of the buffer.
|
||||
*
|
||||
* @param count The maximum number of bytes to read.
|
||||
* @return An array of the read bytes
|
||||
*/
|
||||
public byte[] read(final int count) {
|
||||
byte[] dataReceivedBuffer = new byte[count];
|
||||
int gotten = SerialPortJNI.serialRead(m_portHandle, dataReceivedBuffer, count);
|
||||
if (gotten == count) {
|
||||
return dataReceivedBuffer;
|
||||
}
|
||||
byte[] retVal = new byte[gotten];
|
||||
System.arraycopy(dataReceivedBuffer, 0, retVal, 0, gotten);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write raw bytes to the serial port.
|
||||
*
|
||||
* @param buffer The buffer of bytes to write.
|
||||
* @param count The maximum number of bytes to write.
|
||||
* @return The number of bytes actually written into the port.
|
||||
*/
|
||||
public int write(byte[] buffer, int count) {
|
||||
if (buffer.length < count) {
|
||||
throw new IllegalArgumentException("buffer is too small, must be at least " + count);
|
||||
}
|
||||
return SerialPortJNI.serialWrite(m_portHandle, buffer, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to the serial port.
|
||||
*
|
||||
* @param data The string to write to the serial port.
|
||||
* @return The number of bytes actually written into the port.
|
||||
*/
|
||||
public int writeString(String data) {
|
||||
return write(data.getBytes(StandardCharsets.UTF_8), data.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the timeout of the serial m_port.
|
||||
*
|
||||
* <p>This defines the timeout for transactions with the hardware. It will affect reads if less
|
||||
* bytes are available than the read buffer size (defaults to 1) and very large writes.
|
||||
*
|
||||
* @param timeout The number of seconds to wait for I/O.
|
||||
*/
|
||||
public final void setTimeout(double timeout) {
|
||||
SerialPortJNI.serialSetTimeout(m_portHandle, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the size of the input buffer.
|
||||
*
|
||||
* <p>Specify the amount of data that can be stored before data from the device is returned to
|
||||
* Read. If you want data that is received to be returned immediately, set this to 1.
|
||||
*
|
||||
* <p>It the buffer is not filled before the read timeout expires, all data that has been received
|
||||
* so far will be returned.
|
||||
*
|
||||
* @param size The read buffer size.
|
||||
*/
|
||||
public final void setReadBufferSize(int size) {
|
||||
SerialPortJNI.serialSetReadBufferSize(m_portHandle, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the size of the output buffer.
|
||||
*
|
||||
* <p>Specify the amount of data that can be stored before being transmitted to the device.
|
||||
*
|
||||
* @param size The write buffer size.
|
||||
*/
|
||||
public void setWriteBufferSize(int size) {
|
||||
SerialPortJNI.serialSetWriteBufferSize(m_portHandle, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the flushing behavior of the output buffer.
|
||||
*
|
||||
* <p>When set to kFlushOnAccess, data is synchronously written to the serial port after each call
|
||||
* to either print() or write().
|
||||
*
|
||||
* <p>When set to kFlushWhenFull, data will only be written to the serial port when the buffer is
|
||||
* full or when flush() is called.
|
||||
*
|
||||
* @param mode The write buffer mode.
|
||||
*/
|
||||
public final void setWriteBufferMode(WriteBufferMode mode) {
|
||||
SerialPortJNI.serialSetWriteMode(m_portHandle, (byte) mode.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the output buffer to be written to the port.
|
||||
*
|
||||
* <p>This is used when setWriteBufferMode() is set to kFlushWhenFull to force a flush before the
|
||||
* buffer is full.
|
||||
*/
|
||||
public void flush() {
|
||||
SerialPortJNI.serialFlush(m_portHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the serial port driver to a known state.
|
||||
*
|
||||
* <p>Empty the transmit and receive buffers in the device and formatted I/O.
|
||||
*/
|
||||
public void reset() {
|
||||
SerialPortJNI.serialClear(m_portHandle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// 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.hal.AnalogJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Analog channel class.
|
||||
*
|
||||
* <p>Each analog channel is read from hardware as a 12-bit number representing 0V to 3.3V.
|
||||
*
|
||||
* <p>Connected to each analog channel is an averaging and oversampling engine. This engine
|
||||
* accumulates the specified ( by setAverageBits() and setOversampleBits() ) number of samples
|
||||
* before returning a new value. This is not a sliding window average. The only difference between
|
||||
* the oversampled samples and the averaged samples is that the oversampled samples are simply
|
||||
* accumulated effectively increasing the resolution, while the averaged samples are divided by the
|
||||
* number of samples to retain the resolution, but get more stable values.
|
||||
*/
|
||||
public class AnalogInput implements Sendable, AutoCloseable {
|
||||
int m_port; // explicit no modifier, private and package accessible.
|
||||
private int m_channel;
|
||||
|
||||
/**
|
||||
* Construct an analog channel.
|
||||
*
|
||||
* @param channel The channel number to represent. 0-3 are on-board 4-7 are on the MXP port.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogInput(final int channel) {
|
||||
AnalogJNI.checkAnalogInputChannel(channel);
|
||||
m_channel = channel;
|
||||
|
||||
m_port = AnalogJNI.initializeAnalogInputPort(channel);
|
||||
|
||||
HAL.reportUsage("IO", channel, "AnalogInput");
|
||||
SendableRegistry.add(this, "AnalogInput", channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
AnalogJNI.freeAnalogInputPort(m_port);
|
||||
m_port = 0;
|
||||
m_channel = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sample straight from this channel. The sample is a 12-bit value representing the 0V to
|
||||
* 3.3V range of the A/D converter. The units are in A/D converter codes. Use GetVoltage() to get
|
||||
* the analog value in calibrated units.
|
||||
*
|
||||
* @return A sample straight from this channel.
|
||||
*/
|
||||
public int getValue() {
|
||||
return AnalogJNI.getAnalogValue(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sample from the output of the oversample and average engine for this channel. The sample
|
||||
* is 12-bit + the bits configured in SetOversampleBits(). The value configured in
|
||||
* setAverageBits() will cause this value to be averaged 2^bits number of samples. This is not a
|
||||
* sliding window. The sample will not change until 2^(OversampleBits + AverageBits) samples have
|
||||
* been acquired from this channel. Use getAverageVoltage() to get the analog value in calibrated
|
||||
* units.
|
||||
*
|
||||
* @return A sample from the oversample and average engine for this channel.
|
||||
*/
|
||||
public int getAverageValue() {
|
||||
return AnalogJNI.getAnalogAverageValue(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scaled sample straight from this channel. The value is scaled to units of Volts using the
|
||||
* calibrated scaling data from getLSBWeight() and getOffset().
|
||||
*
|
||||
* @return A scaled sample straight from this channel.
|
||||
*/
|
||||
public double getVoltage() {
|
||||
return AnalogJNI.getAnalogVoltage(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scaled sample from the output of the oversample and average engine for this channel. The
|
||||
* value is scaled to units of Volts using the calibrated scaling data from getLSBWeight() and
|
||||
* getOffset(). Using oversampling will cause this value to be higher resolution, but it will
|
||||
* update more slowly. Using averaging will cause this value to be more stable, but it will update
|
||||
* more slowly.
|
||||
*
|
||||
* @return A scaled sample from the output of the oversample and average engine for this channel.
|
||||
*/
|
||||
public double getAverageVoltage() {
|
||||
return AnalogJNI.getAnalogAverageVoltage(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory scaling the least significant bit weight constant. The least significant bit
|
||||
* weight constant for the channel that was calibrated in manufacturing and stored in an eeprom.
|
||||
*
|
||||
* <p>Volts = ((LSB_Weight * 1e-9) * raw) - (Offset * 1e-9)
|
||||
*
|
||||
* @return Least significant bit weight.
|
||||
*/
|
||||
public long getLSBWeight() {
|
||||
return AnalogJNI.getAnalogLSBWeight(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory scaling offset constant. The offset constant for the channel that was
|
||||
* calibrated in manufacturing and stored in an eeprom.
|
||||
*
|
||||
* <p>Volts = ((LSB_Weight * 1e-9) * raw) - (Offset * 1e-9)
|
||||
*
|
||||
* @return Offset constant.
|
||||
*/
|
||||
public int getOffset() {
|
||||
return AnalogJNI.getAnalogOffset(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channel number.
|
||||
*
|
||||
* @return The channel number.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of averaging bits. This sets the number of averaging bits. The actual number of
|
||||
* averaged samples is 2^bits. The averaging is done automatically in the FPGA.
|
||||
*
|
||||
* @param bits The number of averaging bits.
|
||||
*/
|
||||
public void setAverageBits(final int bits) {
|
||||
AnalogJNI.setAnalogAverageBits(m_port, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of averaging bits. This gets the number of averaging bits from the FPGA. The
|
||||
* actual number of averaged samples is 2^bits. The averaging is done automatically in the FPGA.
|
||||
*
|
||||
* @return The number of averaging bits.
|
||||
*/
|
||||
public int getAverageBits() {
|
||||
return AnalogJNI.getAnalogAverageBits(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of oversample bits. This sets the number of oversample bits. The actual number
|
||||
* of oversampled values is 2^bits. The oversampling is done automatically in the FPGA.
|
||||
*
|
||||
* @param bits The number of oversample bits.
|
||||
*/
|
||||
public void setOversampleBits(final int bits) {
|
||||
AnalogJNI.setAnalogOversampleBits(m_port, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of oversample bits. This gets the number of oversample bits from the FPGA. The
|
||||
* actual number of oversampled values is 2^bits. The oversampling is done automatically in the
|
||||
* FPGA.
|
||||
*
|
||||
* @return The number of oversample bits.
|
||||
*/
|
||||
public int getOversampleBits() {
|
||||
return AnalogJNI.getAnalogOversampleBits(m_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sample rate per channel.
|
||||
*
|
||||
* <p>This is a global setting for all channels. The maximum rate is 500kS/s divided by the number
|
||||
* of channels in use. This is 62500 samples/s per channel if all 8 channels are used.
|
||||
*
|
||||
* @param samplesPerSecond The number of samples per second.
|
||||
*/
|
||||
public static void setGlobalSampleRate(final double samplesPerSecond) {
|
||||
AnalogJNI.setAnalogSampleRate(samplesPerSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current sample rate.
|
||||
*
|
||||
* <p>This assumes one entry in the scan list. This is a global setting for all channels.
|
||||
*
|
||||
* @return Sample rate.
|
||||
*/
|
||||
public static double getGlobalSampleRate() {
|
||||
return AnalogJNI.getAnalogSampleRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this input is used by a simulated device.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
*/
|
||||
public void setSimDevice(SimDevice device) {
|
||||
AnalogJNI.setAnalogInputSimDevice(m_port, device.getNativeHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Analog Input");
|
||||
builder.addDoubleProperty("Value", this::getAverageVoltage, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 counting the number of ticks on a digital input channel. Encoders, Gear tooth
|
||||
* sensors, and counters should all subclass this in order to build more advanced classes for
|
||||
* control and driving.
|
||||
*
|
||||
* <p>All counters will immediately start counting - reset() them if you need them to be zeroed
|
||||
* before use.
|
||||
*/
|
||||
public interface CounterBase {
|
||||
/** The number of edges for the CounterBase to increment or decrement on. */
|
||||
enum EncodingType {
|
||||
/** Count only the rising edge. */
|
||||
k1X(0),
|
||||
/** Count both the rising and falling edge. */
|
||||
k2X(1),
|
||||
/** Count rising and falling on both channels. */
|
||||
k4X(2);
|
||||
|
||||
/** EncodingType value. */
|
||||
public final int value;
|
||||
|
||||
EncodingType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the count.
|
||||
*
|
||||
* @return the count
|
||||
*/
|
||||
int get();
|
||||
|
||||
/** Reset the count to zero. */
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Get the time between the last two edges counted.
|
||||
*
|
||||
* @return the time between the last two ticks in seconds
|
||||
*/
|
||||
double getPeriod();
|
||||
|
||||
/**
|
||||
* Set the maximum time between edges to be considered stalled.
|
||||
*
|
||||
* @param maxPeriod the maximum period in seconds
|
||||
*/
|
||||
void setMaxPeriod(double maxPeriod);
|
||||
|
||||
/**
|
||||
* Determine if the counter is not moving.
|
||||
*
|
||||
* @return true if the counter has not changed for the max period
|
||||
*/
|
||||
boolean getStopped();
|
||||
|
||||
/**
|
||||
* Determine which direction the counter is going.
|
||||
*
|
||||
* @return true for one direction, false for the other
|
||||
*/
|
||||
boolean getDirection();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.hal.DIOJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class to read a digital input. This class will read digital inputs and return the current value
|
||||
* on the channel. Other devices such as encoders, gear tooth sensors, etc. that are implemented
|
||||
* elsewhere will automatically allocate digital inputs and outputs as required. This class is only
|
||||
* for devices like switches etc. that aren't implemented anywhere else.
|
||||
*/
|
||||
public class DigitalInput implements AutoCloseable, Sendable {
|
||||
private final int m_channel;
|
||||
private int m_handle;
|
||||
|
||||
/**
|
||||
* Create an instance of a Digital Input class. Creates a digital input given a channel.
|
||||
*
|
||||
* @param channel the DIO channel for the digital input 0-9 are on-board, 10-25 are on the MXP
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DigitalInput(int channel) {
|
||||
SensorUtil.checkDigitalChannel(channel);
|
||||
m_channel = channel;
|
||||
|
||||
m_handle = DIOJNI.initializeDIOPort(channel, true);
|
||||
|
||||
HAL.reportUsage("IO", channel, "DigitalInput");
|
||||
SendableRegistry.add(this, "DigitalInput", channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
DIOJNI.freeDIOPort(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value from a digital input channel. Retrieve the value of a single digital input
|
||||
* channel from the FPGA.
|
||||
*
|
||||
* @return the status of the digital input
|
||||
*/
|
||||
public boolean get() {
|
||||
return DIOJNI.getDIO(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channel of the digital input.
|
||||
*
|
||||
* @return The GPIO channel number that this object represents.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this input is used by a simulated device.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
*/
|
||||
public void setSimDevice(SimDevice device) {
|
||||
DIOJNI.setDIOSimDevice(m_handle, device.getNativeHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Digital Input");
|
||||
builder.addBooleanProperty("Value", this::get, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// 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.hal.DIOJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class to write digital outputs. This class will write digital outputs. Other devices that are
|
||||
* implemented elsewhere will automatically allocate digital inputs and outputs as required.
|
||||
*/
|
||||
public class DigitalOutput implements AutoCloseable, Sendable {
|
||||
private static final int invalidPwmGenerator = 0;
|
||||
private int m_pwmGenerator = invalidPwmGenerator;
|
||||
|
||||
private final int m_channel;
|
||||
private int m_handle;
|
||||
|
||||
/**
|
||||
* Create an instance of a digital output. Create an instance of a digital output given a channel.
|
||||
*
|
||||
* @param channel the DIO channel to use for the digital output. 0-9 are on-board, 10-25 are on
|
||||
* the MXP
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DigitalOutput(int channel) {
|
||||
SensorUtil.checkDigitalChannel(channel);
|
||||
m_channel = channel;
|
||||
|
||||
m_handle = DIOJNI.initializeDIOPort(channel, false);
|
||||
|
||||
HAL.reportUsage("IO", channel, "DigitalOutput");
|
||||
SendableRegistry.add(this, "DigitalOutput", channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
// Disable the pwm only if we have allocated it
|
||||
if (m_pwmGenerator != invalidPwmGenerator) {
|
||||
disablePWM();
|
||||
}
|
||||
DIOJNI.freeDIOPort(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a digital output.
|
||||
*
|
||||
* @param value true is on, off is false
|
||||
*/
|
||||
public void set(boolean value) {
|
||||
DIOJNI.setDIO(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value being output from the Digital Output.
|
||||
*
|
||||
* @return the state of the digital output.
|
||||
*/
|
||||
public boolean get() {
|
||||
return DIOJNI.getDIO(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GPIO channel number that this object represents.
|
||||
*
|
||||
* @return The GPIO channel number.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a single pulse on the digital output line.
|
||||
*
|
||||
* <p>Send a single pulse on the digital output line where the pulse duration is specified in
|
||||
* seconds. Maximum of 65535 microseconds.
|
||||
*
|
||||
* @param pulseLength The pulse length in seconds
|
||||
*/
|
||||
public void pulse(final double pulseLength) {
|
||||
DIOJNI.pulse(m_handle, pulseLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the pulse is still going. Determine if a previously started pulse is still going.
|
||||
*
|
||||
* @return true if pulsing
|
||||
*/
|
||||
public boolean isPulsing() {
|
||||
return DIOJNI.isPulsing(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the PWM frequency of the PWM output on a Digital Output line.
|
||||
*
|
||||
* <p>The valid range is from 0.6 Hz to 19 kHz. The frequency resolution is logarithmic.
|
||||
*
|
||||
* <p>There is only one PWM frequency for all channels.
|
||||
*
|
||||
* @param rate The frequency to output all digital output PWM signals.
|
||||
*/
|
||||
public void setPWMRate(double rate) {
|
||||
DIOJNI.setDigitalPWMRate(rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a PWM PPS (Pulse Per Second) Output on this line.
|
||||
*
|
||||
* <p>Allocate one of the 6 DO PWM generator resources.
|
||||
*
|
||||
* <p>Supply the duty-cycle to output.
|
||||
*
|
||||
* <p>The resolution of the duty cycle is 8-bit.
|
||||
*
|
||||
* @param dutyCycle The duty-cycle to start generating. [0..1]
|
||||
*/
|
||||
public void enablePPS(double dutyCycle) {
|
||||
if (m_pwmGenerator != invalidPwmGenerator) {
|
||||
return;
|
||||
}
|
||||
m_pwmGenerator = DIOJNI.allocateDigitalPWM();
|
||||
DIOJNI.setDigitalPWMPPS(m_pwmGenerator, dutyCycle);
|
||||
DIOJNI.setDigitalPWMOutputChannel(m_pwmGenerator, m_channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a PWM Output on this line.
|
||||
*
|
||||
* <p>Allocate one of the 6 DO PWM generator resources.
|
||||
*
|
||||
* <p>Supply the initial duty-cycle to output in order to avoid a glitch when first starting.
|
||||
*
|
||||
* <p>The resolution of the duty cycle is 8-bit for low frequencies (1kHz or less) but is reduced
|
||||
* the higher the frequency of the PWM signal is.
|
||||
*
|
||||
* @param initialDutyCycle The duty-cycle to start generating. [0..1]
|
||||
*/
|
||||
public void enablePWM(double initialDutyCycle) {
|
||||
if (m_pwmGenerator != invalidPwmGenerator) {
|
||||
return;
|
||||
}
|
||||
m_pwmGenerator = DIOJNI.allocateDigitalPWM();
|
||||
DIOJNI.setDigitalPWMDutyCycle(m_pwmGenerator, initialDutyCycle);
|
||||
DIOJNI.setDigitalPWMOutputChannel(m_pwmGenerator, m_channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change this line from a PWM output back to a static Digital Output line.
|
||||
*
|
||||
* <p>Free up one of the 6 DO PWM generator resources that were in use.
|
||||
*/
|
||||
public void disablePWM() {
|
||||
if (m_pwmGenerator == invalidPwmGenerator) {
|
||||
return;
|
||||
}
|
||||
// Disable the output by routing to a dead bit.
|
||||
DIOJNI.setDigitalPWMOutputChannel(m_pwmGenerator, SensorUtil.kDigitalChannels);
|
||||
DIOJNI.freeDigitalPWM(m_pwmGenerator);
|
||||
m_pwmGenerator = invalidPwmGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the duty-cycle that is being generated on the line.
|
||||
*
|
||||
* <p>The resolution of the duty cycle is 8-bit for low frequencies (1kHz or less) but is reduced
|
||||
* the higher the frequency of the PWM signal is.
|
||||
*
|
||||
* @param dutyCycle The duty-cycle to change to. [0..1]
|
||||
*/
|
||||
public void updateDutyCycle(double dutyCycle) {
|
||||
if (m_pwmGenerator == invalidPwmGenerator) {
|
||||
return;
|
||||
}
|
||||
DIOJNI.setDigitalPWMDutyCycle(m_pwmGenerator, dutyCycle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this input is used by a simulated device.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
*/
|
||||
public void setSimDevice(SimDevice device) {
|
||||
DIOJNI.setDIOSimDevice(m_handle, device.getNativeHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Digital Output");
|
||||
builder.addBooleanProperty("Value", this::get, this::set);
|
||||
}
|
||||
}
|
||||
160
wpilibj/src/main/java/org/wpilib/hardware/discrete/PWM.java
Normal file
160
wpilibj/src/main/java/org/wpilib/hardware/discrete/PWM.java
Normal file
@@ -0,0 +1,160 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.PWMJNI;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class implements the PWM generation in the FPGA.
|
||||
*
|
||||
* <p>The values supplied as arguments for PWM outputs range from -1.0 to 1.0. They are mapped to
|
||||
* the microseconds to keep the pulse high, with a range of 0 (off) to 4096. Changes are immediately
|
||||
* sent to the FPGA, and the update occurs at the next FPGA cycle (5.05ms). There is no delay.
|
||||
*/
|
||||
public class PWM implements Sendable, AutoCloseable {
|
||||
/** Represents the output period in microseconds. */
|
||||
public enum OutputPeriod {
|
||||
/** Pulse every 5ms. */
|
||||
k5Ms,
|
||||
/** Pulse every 10ms. */
|
||||
k10Ms,
|
||||
/** Pulse every 20ms. */
|
||||
k20Ms
|
||||
}
|
||||
|
||||
private final int m_channel;
|
||||
|
||||
private int m_handle;
|
||||
|
||||
/**
|
||||
* Allocate a PWM given a channel.
|
||||
*
|
||||
* <p>Checks channel value range and allocates the appropriate channel. The allocation is only
|
||||
* done to help users ensure that they don't double assign channels.
|
||||
*
|
||||
* <p>By default, adds itself to SendableRegistry.
|
||||
*
|
||||
* @param channel The PWM channel number. 0-9 are on-board, 10-19 are on the MXP port
|
||||
*/
|
||||
public PWM(final int channel) {
|
||||
this(channel, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a PWM given a channel.
|
||||
*
|
||||
* @param channel The PWM channel number. 0-9 are on-board, 10-19 are on the MXP port
|
||||
* @param registerSendable If true, adds this instance to SendableRegistry
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public PWM(final int channel, final boolean registerSendable) {
|
||||
SensorUtil.checkPWMChannel(channel);
|
||||
m_channel = channel;
|
||||
|
||||
m_handle = PWMJNI.initializePWMPort(channel);
|
||||
|
||||
setDisabled();
|
||||
|
||||
HAL.reportUsage("IO", channel, "PWM");
|
||||
if (registerSendable) {
|
||||
SendableRegistry.add(this, "PWM", channel);
|
||||
}
|
||||
}
|
||||
|
||||
/** Free the resource associated with the PWM channel and set the value to 0. */
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
if (m_handle == 0) {
|
||||
return;
|
||||
}
|
||||
setDisabled();
|
||||
PWMJNI.freePWMPort(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the channel number associated with the PWM Object.
|
||||
*
|
||||
* @return The channel number.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PWM value directly to the hardware.
|
||||
*
|
||||
* <p>Write a microsecond pulse value to a PWM channel.
|
||||
*
|
||||
* @param microsecondPulseTime Microsecond pulse PWM value. Range 0 - 4096.
|
||||
*/
|
||||
public void setPulseTimeMicroseconds(int microsecondPulseTime) {
|
||||
PWMJNI.setPulseTimeMicroseconds(m_handle, microsecondPulseTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PWM value directly from the hardware.
|
||||
*
|
||||
* <p>Read a raw value from a PWM channel.
|
||||
*
|
||||
* @return Microsecond pulse PWM control value. Range: 0 - 4096.
|
||||
*/
|
||||
public int getPulseTimeMicroseconds() {
|
||||
return PWMJNI.getPulseTimeMicroseconds(m_handle);
|
||||
}
|
||||
|
||||
/** Temporarily disables the PWM output. The next set call will re-enable the output. */
|
||||
public final void setDisabled() {
|
||||
setPulseTimeMicroseconds(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PWM output period.
|
||||
*
|
||||
* @param mult The output period to apply to this channel
|
||||
*/
|
||||
public void setOutputPeriod(OutputPeriod mult) {
|
||||
int scale =
|
||||
switch (mult) {
|
||||
case k20Ms -> 3;
|
||||
case k10Ms -> 1;
|
||||
case k5Ms -> 0;
|
||||
};
|
||||
|
||||
PWMJNI.setPWMOutputPeriod(m_handle, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying handle.
|
||||
*
|
||||
* @return Underlying PWM handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this input is used by a simulated device.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
*/
|
||||
public void setSimDevice(SimDevice device) {
|
||||
PWMJNI.setPWMSimDevice(m_handle, device.getNativeHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("PWM");
|
||||
builder.setActuator(true);
|
||||
builder.addDoubleProperty(
|
||||
"Value", this::getPulseTimeMicroseconds, value -> setPulseTimeMicroseconds((int) value));
|
||||
}
|
||||
}
|
||||
196
wpilibj/src/main/java/org/wpilib/hardware/imu/OnboardIMU.java
Normal file
196
wpilibj/src/main/java/org/wpilib/hardware/imu/OnboardIMU.java
Normal file
@@ -0,0 +1,196 @@
|
||||
// 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.hal.IMUJNI;
|
||||
import edu.wpi.first.math.geometry.Quaternion;
|
||||
import edu.wpi.first.math.geometry.Rotation2d;
|
||||
import edu.wpi.first.math.geometry.Rotation3d;
|
||||
|
||||
/** SystemCore onboard IMU. */
|
||||
public class OnboardIMU {
|
||||
/** A mount orientation of SystemCore. */
|
||||
public enum MountOrientation {
|
||||
/** Flat (mounted parallel to the ground). */
|
||||
kFlat,
|
||||
/** Landscape (vertically mounted with long edge of SystemCore parallel to the ground). */
|
||||
kLandscape,
|
||||
/** Portrait (vertically mounted with the short edge of SystemCore parallel to the ground). */
|
||||
kPortrait
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a handle to the SystemCore onboard IMU.
|
||||
*
|
||||
* @param mountOrientation the mount orientation of SystemCore to determine yaw.
|
||||
*/
|
||||
public OnboardIMU(MountOrientation mountOrientation) {
|
||||
m_mountOrientation = mountOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the yaw value in radians.
|
||||
*
|
||||
* @return yaw value in radians
|
||||
*/
|
||||
public double getYawRadians() {
|
||||
return getYawNoOffset() - m_yawOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the current yaw value to 0. Future reads of the yaw value will be relative to the current
|
||||
* orientation.
|
||||
*/
|
||||
public void resetYaw() {
|
||||
m_yawOffset = getYawNoOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the yaw as a Rotation2d.
|
||||
*
|
||||
* @return yaw
|
||||
*/
|
||||
public Rotation2d getRotation2d() {
|
||||
return new Rotation2d(getYawRadians());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 3D orientation as a Rotation3d.
|
||||
*
|
||||
* @return 3D orientation
|
||||
*/
|
||||
public Rotation3d getRotation3d() {
|
||||
return new Rotation3d(getQuaternion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 3D orientation as a Quaternion.
|
||||
*
|
||||
* @return 3D orientation
|
||||
*/
|
||||
public Quaternion getQuaternion() {
|
||||
double[] quatRaw = new double[4];
|
||||
IMUJNI.getIMUQuaternion(quatRaw);
|
||||
return new Quaternion(quatRaw[0], quatRaw[1], quatRaw[2], quatRaw[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the angle about the X axis of the IMU in radians.
|
||||
*
|
||||
* @return angle about the X axis in radians
|
||||
*/
|
||||
public double getAngleX() {
|
||||
return getRawEulerAngles()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the angle about the Y axis of the IMU in radians.
|
||||
*
|
||||
* @return angle about the Y axis in radians
|
||||
*/
|
||||
public double getAngleY() {
|
||||
return getRawEulerAngles()[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the angle about the Z axis of the IMU in radians.
|
||||
*
|
||||
* @return angle about the Z axis in radians
|
||||
*/
|
||||
public double getAngleZ() {
|
||||
return getRawEulerAngles()[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the angular rate about the X axis of the IMU in radians per second.
|
||||
*
|
||||
* @return angular rate about the X axis in radians per second
|
||||
*/
|
||||
public double getGyroRateX() {
|
||||
return getRawGyroRates()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the angular rate about the Y axis of the IMU in radians per second.
|
||||
*
|
||||
* @return angular rate about the Y axis in radians per second
|
||||
*/
|
||||
public double getGyroRateY() {
|
||||
return getRawGyroRates()[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the angular rate about the Z axis of the IMU in radians per second.
|
||||
*
|
||||
* @return angular rate about the Z axis in radians per second
|
||||
*/
|
||||
public double getGyroRateZ() {
|
||||
return getRawGyroRates()[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the acceleration along the X axis of the IMU in meters per second squared.
|
||||
*
|
||||
* @return acceleration along the X axis in meters per second squared
|
||||
*/
|
||||
public double getAccelX() {
|
||||
return getRawAccels()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the acceleration along the X axis of the IMU in meters per second squared.
|
||||
*
|
||||
* @return acceleration along the X axis in meters per second squared
|
||||
*/
|
||||
public double getAccelY() {
|
||||
return getRawAccels()[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the acceleration along the X axis of the IMU in meters per second squared.
|
||||
*
|
||||
* @return acceleration along the X axis in meters per second squared
|
||||
*/
|
||||
public double getAccelZ() {
|
||||
return getRawAccels()[2];
|
||||
}
|
||||
|
||||
private double[] getRawEulerAngles() {
|
||||
double[] anglesRaw = new double[3];
|
||||
switch (m_mountOrientation) {
|
||||
case kFlat -> IMUJNI.getIMUEulerAnglesFlat(anglesRaw);
|
||||
case kLandscape -> IMUJNI.getIMUEulerAnglesLandscape(anglesRaw);
|
||||
case kPortrait -> IMUJNI.getIMUEulerAnglesPortrait(anglesRaw);
|
||||
default -> {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
return anglesRaw;
|
||||
}
|
||||
|
||||
private double[] getRawGyroRates() {
|
||||
double[] ratesRaw = new double[3];
|
||||
IMUJNI.getIMUGyroRates(ratesRaw);
|
||||
return ratesRaw;
|
||||
}
|
||||
|
||||
private double[] getRawAccels() {
|
||||
double[] accelsRaw = new double[3];
|
||||
IMUJNI.getIMUAcceleration(accelsRaw);
|
||||
return accelsRaw;
|
||||
}
|
||||
|
||||
private double getYawNoOffset() {
|
||||
return switch (m_mountOrientation) {
|
||||
case kFlat -> IMUJNI.getIMUYawFlat();
|
||||
case kLandscape -> IMUJNI.getIMUYawLandscape();
|
||||
case kPortrait -> IMUJNI.getIMUYawPortrait();
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private final MountOrientation m_mountOrientation;
|
||||
private double m_yawOffset;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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.hal.AddressableLEDJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
|
||||
/**
|
||||
* A class for driving addressable LEDs, such as WS2812B, WS2815, and NeoPixels.
|
||||
*
|
||||
* <p>Some LEDs use a different color order than the default GRB. The color order is configurable
|
||||
* using {@link #setColorOrder(ColorOrder)}.
|
||||
*
|
||||
* <p>Up to 1024 LEDs may be controlled in total across all AddressableLED instances. A single
|
||||
* global buffer is used for all instances. The start position used for LED data for the output is
|
||||
* set via SetStart() and the length of the strip is set via SetLength(). Both of these default to
|
||||
* zero, so multiple instances will access the same pixel data unless SetStart() is called to adjust
|
||||
* the starting point.
|
||||
*/
|
||||
public class AddressableLED implements AutoCloseable {
|
||||
/** Order that color data is sent over the wire. */
|
||||
public enum ColorOrder {
|
||||
/** RGB order. */
|
||||
kRGB(AddressableLEDJNI.COLOR_ORDER_RGB),
|
||||
/** RBG order. */
|
||||
kRBG(AddressableLEDJNI.COLOR_ORDER_RBG),
|
||||
/** BGR order. */
|
||||
kBGR(AddressableLEDJNI.COLOR_ORDER_BGR),
|
||||
/** BRG order. */
|
||||
kBRG(AddressableLEDJNI.COLOR_ORDER_BRG),
|
||||
/** GBR order. */
|
||||
kGBR(AddressableLEDJNI.COLOR_ORDER_GBR),
|
||||
/** GRB order. This is the default order. */
|
||||
kGRB(AddressableLEDJNI.COLOR_ORDER_GRB);
|
||||
|
||||
/** The native value for this ColorOrder. */
|
||||
public final int value;
|
||||
|
||||
ColorOrder(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a color order from an int value.
|
||||
*
|
||||
* @param value int value
|
||||
* @return color order
|
||||
*/
|
||||
public ColorOrder fromValue(int value) {
|
||||
return switch (value) {
|
||||
case AddressableLEDJNI.COLOR_ORDER_RBG -> kRBG;
|
||||
case AddressableLEDJNI.COLOR_ORDER_BGR -> kBGR;
|
||||
case AddressableLEDJNI.COLOR_ORDER_BRG -> kBRG;
|
||||
case AddressableLEDJNI.COLOR_ORDER_GRB -> kGRB;
|
||||
case AddressableLEDJNI.COLOR_ORDER_GBR -> kGBR;
|
||||
case AddressableLEDJNI.COLOR_ORDER_RGB -> kRGB;
|
||||
default -> kGRB;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private final int m_channel;
|
||||
private final int m_handle;
|
||||
private int m_start;
|
||||
private int m_length;
|
||||
private ColorOrder m_colorOrder = ColorOrder.kGRB;
|
||||
|
||||
/**
|
||||
* Constructs a new driver for a specific channel.
|
||||
*
|
||||
* @param channel the output channel to use
|
||||
*/
|
||||
public AddressableLED(int channel) {
|
||||
m_channel = channel;
|
||||
m_handle = AddressableLEDJNI.initialize(channel);
|
||||
HAL.reportUsage("IO", channel, "AddressableLED");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_handle != 0) {
|
||||
AddressableLEDJNI.free(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output channel.
|
||||
*
|
||||
* @return the output channel
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color order for this AddressableLED. The default order is GRB.
|
||||
*
|
||||
* <p>This will take effect on the next call to {@link #setData(AddressableLEDBuffer)}.
|
||||
*
|
||||
* @param order the color order
|
||||
*/
|
||||
public void setColorOrder(ColorOrder order) {
|
||||
m_colorOrder = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display start of the LED strip in the global buffer.
|
||||
*
|
||||
* @param start the strip start, in LEDs
|
||||
*/
|
||||
public void setStart(int start) {
|
||||
m_start = start;
|
||||
AddressableLEDJNI.setStart(m_handle, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display start of the LED strip in the global buffer.
|
||||
*
|
||||
* @return the strip start, in LEDs
|
||||
*/
|
||||
public int getStart() {
|
||||
return m_start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the length of the LED strip.
|
||||
*
|
||||
* @param length the strip length, in LEDs
|
||||
*/
|
||||
public void setLength(int length) {
|
||||
m_length = length;
|
||||
AddressableLEDJNI.setLength(m_handle, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the LED output data.
|
||||
*
|
||||
* <p>This will write to the global buffer starting at the location set by setStart() and up to
|
||||
* the length set by setLength().
|
||||
*
|
||||
* @param buffer the buffer to write
|
||||
*/
|
||||
public void setData(AddressableLEDBuffer buffer) {
|
||||
AddressableLEDJNI.setData(
|
||||
m_start,
|
||||
m_colorOrder.value,
|
||||
buffer.m_buffer,
|
||||
0,
|
||||
3 * Math.min(m_length, buffer.getLength()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the LED output data at an arbitrary location in the global buffer.
|
||||
*
|
||||
* @param start the start location, in LEDs
|
||||
* @param colorOrder the color order
|
||||
* @param buffer the buffer to write
|
||||
*/
|
||||
public static void setGlobalData(int start, ColorOrder colorOrder, AddressableLEDBuffer buffer) {
|
||||
AddressableLEDJNI.setData(start, colorOrder.value, buffer.m_buffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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;
|
||||
|
||||
/** Buffer storage for Addressable LEDs. */
|
||||
public class AddressableLEDBuffer implements LEDReader, LEDWriter {
|
||||
byte[] m_buffer;
|
||||
|
||||
/**
|
||||
* Constructs a new LED buffer with the specified length.
|
||||
*
|
||||
* @param length The length of the buffer in pixels
|
||||
*/
|
||||
public AddressableLEDBuffer(int length) {
|
||||
m_buffer = new byte[length * 3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific led in the buffer.
|
||||
*
|
||||
* @param index the index to write
|
||||
* @param r the r value [0-255]
|
||||
* @param g the g value [0-255]
|
||||
* @param b the b value [0-255]
|
||||
*/
|
||||
@Override
|
||||
public void setRGB(int index, int r, int g, int b) {
|
||||
m_buffer[index * 3] = (byte) r;
|
||||
m_buffer[(index * 3) + 1] = (byte) g;
|
||||
m_buffer[(index * 3) + 2] = (byte) b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the buffer length.
|
||||
*
|
||||
* @return the buffer length
|
||||
*/
|
||||
@Override
|
||||
public int getLength() {
|
||||
return m_buffer.length / 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the red channel of the color at the specified index.
|
||||
*
|
||||
* @param index the index of the LED to read
|
||||
* @return the value of the red channel, from [0, 255]
|
||||
*/
|
||||
@Override
|
||||
public int getRed(int index) {
|
||||
return m_buffer[index * 3] & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the green channel of the color at the specified index.
|
||||
*
|
||||
* @param index the index of the LED to read
|
||||
* @return the value of the green channel, from [0, 255]
|
||||
*/
|
||||
@Override
|
||||
public int getGreen(int index) {
|
||||
return m_buffer[index * 3 + 1] & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blue channel of the color at the specified index.
|
||||
*
|
||||
* @param index the index of the LED to read
|
||||
* @return the value of the blue channel, from [0, 255]
|
||||
*/
|
||||
@Override
|
||||
public int getBlue(int index) {
|
||||
return m_buffer[index * 3 + 2] & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view of a subsection of this data buffer, starting from (and including) {@code
|
||||
* startingIndex} and ending on (and including) {@code endingIndex}. Views cannot be written
|
||||
* directly to an {@link AddressableLED}, but are useful tools for logically separating different
|
||||
* sections of an LED strip for independent control.
|
||||
*
|
||||
* @param startingIndex the first index in this buffer that the view should encompass (inclusive)
|
||||
* @param endingIndex the last index in this buffer that the view should encompass (inclusive)
|
||||
* @return the view object
|
||||
*/
|
||||
public AddressableLEDBufferView createView(int startingIndex, int endingIndex) {
|
||||
return new AddressableLEDBufferView(this, startingIndex, endingIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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 static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import edu.wpi.first.wpilibj.util.Color;
|
||||
import edu.wpi.first.wpilibj.util.Color8Bit;
|
||||
|
||||
/**
|
||||
* A view of another addressable LED buffer. Views CANNOT be written directly to an LED strip; the
|
||||
* backing buffer must be written instead. However, views provide an easy way to split a large LED
|
||||
* strip into smaller sections (which may be reversed from the orientation of the LED strip as a
|
||||
* whole) that can be animated individually without modifying LEDs outside those sections.
|
||||
*/
|
||||
public class AddressableLEDBufferView implements LEDReader, LEDWriter {
|
||||
private final LEDReader m_backingReader;
|
||||
private final LEDWriter m_backingWriter;
|
||||
private final int m_startingIndex;
|
||||
private final int m_endingIndex;
|
||||
private final int m_length;
|
||||
|
||||
/**
|
||||
* Creates a new view of a buffer. A view will be reversed if the starting index is after the
|
||||
* ending index; writing front-to-back in the view will write in the back-to-front direction on
|
||||
* the underlying buffer.
|
||||
*
|
||||
* @param backingBuffer the backing buffer to view
|
||||
* @param startingIndex the index of the LED in the backing buffer that the view should start from
|
||||
* @param endingIndex the index of the LED in the backing buffer that the view should end on
|
||||
* @param <B> the type of the buffer object to create a view for
|
||||
*/
|
||||
public <B extends LEDReader & LEDWriter> AddressableLEDBufferView(
|
||||
B backingBuffer, int startingIndex, int endingIndex) {
|
||||
this(
|
||||
requireNonNullParam(backingBuffer, "backingBuffer", "AddressableLEDBufferView"),
|
||||
backingBuffer,
|
||||
startingIndex,
|
||||
endingIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new view of a buffer. A view will be reversed if the starting index is after the
|
||||
* ending index; writing front-to-back in the view will write in the back-to-front direction on
|
||||
* the underlying buffer.
|
||||
*
|
||||
* @param backingReader the backing LED data reader
|
||||
* @param backingWriter the backing LED data writer
|
||||
* @param startingIndex the index of the LED in the backing buffer that the view should start from
|
||||
* @param endingIndex the index of the LED in the backing buffer that the view should end on
|
||||
*/
|
||||
public AddressableLEDBufferView(
|
||||
LEDReader backingReader, LEDWriter backingWriter, int startingIndex, int endingIndex) {
|
||||
requireNonNullParam(backingReader, "backingReader", "AddressableLEDBufferView");
|
||||
requireNonNullParam(backingWriter, "backingWriter", "AddressableLEDBufferView");
|
||||
|
||||
if (startingIndex < 0 || startingIndex >= backingReader.getLength()) {
|
||||
throw new IndexOutOfBoundsException("Start index out of range: " + startingIndex);
|
||||
}
|
||||
if (endingIndex < 0 || endingIndex >= backingReader.getLength()) {
|
||||
throw new IndexOutOfBoundsException("End index out of range: " + endingIndex);
|
||||
}
|
||||
|
||||
m_backingReader = backingReader;
|
||||
m_backingWriter = backingWriter;
|
||||
|
||||
m_startingIndex = startingIndex;
|
||||
m_endingIndex = endingIndex;
|
||||
m_length = Math.abs(endingIndex - startingIndex) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view that operates on the same range as this one, but goes in reverse order. This is
|
||||
* useful for serpentine runs of LED strips connected front-to-end; simply reverse the view for
|
||||
* reversed sections and animations will move in the same physical direction along both strips.
|
||||
*
|
||||
* @return the reversed view
|
||||
*/
|
||||
public AddressableLEDBufferView reversed() {
|
||||
return new AddressableLEDBufferView(this, m_length - 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return m_length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRGB(int index, int r, int g, int b) {
|
||||
m_backingWriter.setRGB(nativeIndex(index), r, g, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getLED(int index) {
|
||||
// override to delegate to the backing buffer to avoid 3x native index lookups & bounds checks
|
||||
return m_backingReader.getLED(nativeIndex(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color8Bit getLED8Bit(int index) {
|
||||
// override to delegate to the backing buffer to avoid 3x native index lookups & bounds checks
|
||||
return m_backingReader.getLED8Bit(nativeIndex(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(int index) {
|
||||
return m_backingReader.getRed(nativeIndex(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(int index) {
|
||||
return m_backingReader.getGreen(nativeIndex(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(int index) {
|
||||
return m_backingReader.getBlue(nativeIndex(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this view is reversed with respect to its backing buffer.
|
||||
*
|
||||
* @return true if the view is reversed, false otherwise
|
||||
*/
|
||||
public boolean isReversed() {
|
||||
return m_endingIndex < m_startingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a view-local index in the range [start, end] to a global index in the range [0,
|
||||
* length].
|
||||
*
|
||||
* @param viewIndex the view-local index
|
||||
* @return the corresponding global index
|
||||
* @throws IndexOutOfBoundsException if the view index is not contained within the bounds of this
|
||||
* view
|
||||
*/
|
||||
private int nativeIndex(int viewIndex) {
|
||||
if (isReversed()) {
|
||||
// 0 1 2 3 4 5 6 7 8 9 10
|
||||
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
|
||||
// [_, _, _, _, (d, c, b, a), _, _, _]
|
||||
// ↑ ↑ ↑ ↑
|
||||
// 3 2 1 0
|
||||
if (viewIndex < 0 || viewIndex > m_startingIndex) {
|
||||
throw new IndexOutOfBoundsException(viewIndex);
|
||||
}
|
||||
return m_startingIndex - viewIndex;
|
||||
} else {
|
||||
// 0 1 2 3 4 5 6 7 8 9 10
|
||||
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
|
||||
// [_, _, _, _, (a, b, c, d), _, _, _]
|
||||
// ↑ ↑ ↑ ↑
|
||||
// 0 1 2 3
|
||||
if (viewIndex < 0 || viewIndex > m_endingIndex) {
|
||||
throw new IndexOutOfBoundsException(viewIndex);
|
||||
}
|
||||
return m_startingIndex + viewIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
689
wpilibj/src/main/java/org/wpilib/hardware/led/LEDPattern.java
Normal file
689
wpilibj/src/main/java/org/wpilib/hardware/led/LEDPattern.java
Normal file
@@ -0,0 +1,689 @@
|
||||
// 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 static edu.wpi.first.units.Units.Meters;
|
||||
import static edu.wpi.first.units.Units.Microsecond;
|
||||
import static edu.wpi.first.units.Units.Microseconds;
|
||||
import static edu.wpi.first.units.Units.Value;
|
||||
|
||||
import edu.wpi.first.units.collections.LongToObjectHashMap;
|
||||
import edu.wpi.first.units.measure.Dimensionless;
|
||||
import edu.wpi.first.units.measure.Distance;
|
||||
import edu.wpi.first.units.measure.Frequency;
|
||||
import edu.wpi.first.units.measure.LinearVelocity;
|
||||
import edu.wpi.first.units.measure.Time;
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import edu.wpi.first.wpilibj.util.Color;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.DoubleSupplier;
|
||||
|
||||
/**
|
||||
* An LED pattern controls lights on an LED strip to command patterns of color that may change over
|
||||
* time. Dynamic patterns should synchronize on an external clock for timed-based animations ({@link
|
||||
* WPIUtilJNI#now()} is recommended, since it can be mocked in simulation and unit tests), or on
|
||||
* some other dynamic input (see {@link #synchronizedBlink(BooleanSupplier)}, for example).
|
||||
*
|
||||
* <p>Patterns should be updated periodically in order for animations to play smoothly. For example,
|
||||
* a hypothetical LED subsystem could create a {@code Command} that will continuously apply the
|
||||
* pattern to its LED data buffer as part of the main periodic loop.
|
||||
*
|
||||
* <pre><code>
|
||||
* public class LEDs extends SubsystemBase {
|
||||
* private final AddressableLED m_led = new AddressableLED(0);
|
||||
* private final AddressableLEDBuffer m_ledData = new AddressableLEDBuffer(120);
|
||||
*
|
||||
* public LEDs() {
|
||||
* m_led.setLength(120);
|
||||
* m_led.start();
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void periodic() {
|
||||
* m_led.writeData(m_ledData);
|
||||
* }
|
||||
*
|
||||
* public Command runPattern(LEDPattern pattern) {
|
||||
* return run(() -> pattern.applyTo(m_ledData));
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* <p>LED patterns are stateless, and as such can be applied to multiple LED strips (or different
|
||||
* sections of the same LED strip, since the roboRIO can only drive a single LED strip). In this
|
||||
* example, we split the single buffer into two views - one for the section of the LED strip on the
|
||||
* left side of a robot, and another view for the section of LEDs on the right side. The same
|
||||
* pattern is able to be applied to both sides.
|
||||
*
|
||||
* <pre><code>
|
||||
* public class LEDs extends SubsystemBase {
|
||||
* private final AddressableLED m_led = new AddressableLED(0);
|
||||
* private final AddressableLEDBuffer m_ledData = new AddressableLEDBuffer(60);
|
||||
* private final AddressableLEDBufferView m_leftData = m_ledData.createView(0, 29);
|
||||
* private final AddressableLEDBufferView m_rightData = m_ledData.createView(30, 59).reversed();
|
||||
*
|
||||
* public LEDs() {
|
||||
* m_led.setLength(60);
|
||||
* m_led.start();
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void periodic() {
|
||||
* m_led.writeData(m_ledData);
|
||||
* }
|
||||
*
|
||||
* public Command runPattern(LEDPattern pattern) {
|
||||
* // Use the single input pattern to drive both sides
|
||||
* return runSplitPatterns(pattern, pattern);
|
||||
* }
|
||||
*
|
||||
* public Command runSplitPatterns(LEDPattern left, LEDPattern right) {
|
||||
* return run(() -> {
|
||||
* left.applyTo(m_leftData);
|
||||
* right.applyTo(m_rightData);
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LEDPattern {
|
||||
/** A functional interface for index mapping functions. */
|
||||
@FunctionalInterface
|
||||
interface IndexMapper {
|
||||
/**
|
||||
* Maps the index.
|
||||
*
|
||||
* @param bufLen Length of the buffer
|
||||
* @param index The index to map
|
||||
* @return The mapped index
|
||||
*/
|
||||
int apply(int bufLen, int index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the pattern to an LED buffer. Dynamic animations should be called periodically (such as
|
||||
* with a command or with a periodic method) to refresh the buffer over time.
|
||||
*
|
||||
* <p>This method is intentionally designed to use separate objects for reading and writing data.
|
||||
* By splitting them up, we can easily modify the behavior of some base pattern to make it {@link
|
||||
* #scrollAtRelativeSpeed(Frequency) scroll}, {@link #blink(Time, Time) blink}, or {@link
|
||||
* #breathe(Time) breathe} by intercepting the data writes to transform their behavior to whatever
|
||||
* we like.
|
||||
*
|
||||
* @param reader data reader for accessing buffer length and current colors
|
||||
* @param writer data writer for setting new LED colors on the buffer
|
||||
*/
|
||||
void applyTo(LEDReader reader, LEDWriter writer);
|
||||
|
||||
/**
|
||||
* Convenience for {@link #applyTo(LEDReader, LEDWriter)} when one object provides both a read and
|
||||
* a write interface. This is most helpful for playing an animated pattern directly on an {@link
|
||||
* AddressableLEDBuffer} for the sake of code clarity.
|
||||
*
|
||||
* <pre><code>
|
||||
* AddressableLEDBuffer data = new AddressableLEDBuffer(120);
|
||||
* LEDPattern pattern = ...
|
||||
*
|
||||
* void periodic() {
|
||||
* pattern.applyTo(data);
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @param readWriter the object to use for both reading and writing to a set of LEDs
|
||||
* @param <T> the type of the object that can both read and write LED data
|
||||
*/
|
||||
default <T extends LEDReader & LEDWriter> void applyTo(T readWriter) {
|
||||
applyTo(readWriter, readWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern with remapped indices.
|
||||
*
|
||||
* @param indexMapper the index mapper
|
||||
* @return the mapped pattern
|
||||
*/
|
||||
default LEDPattern mapIndex(IndexMapper indexMapper) {
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
applyTo(
|
||||
new LEDReader() {
|
||||
@Override
|
||||
public int getLength() {
|
||||
return reader.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(int index) {
|
||||
return reader.getRed(indexMapper.apply(bufLen, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(int index) {
|
||||
return reader.getGreen(indexMapper.apply(bufLen, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(int index) {
|
||||
return reader.getBlue(indexMapper.apply(bufLen, index));
|
||||
}
|
||||
},
|
||||
(i, r, g, b) -> writer.setRGB(indexMapper.apply(bufLen, i), r, g, b));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that displays this one in reverse. Scrolling patterns will scroll in the
|
||||
* opposite direction (but at the same speed). It will treat the end of an LED strip as the start,
|
||||
* and the start of the strip as the end. This can be useful for making ping-pong patterns that
|
||||
* travel from one end of an LED strip to the other, then reverse direction and move back to the
|
||||
* start. This can also be useful when working with LED strips connected in a serpentine pattern
|
||||
* (where the start of one strip is connected to the end of the previous one); however, consider
|
||||
* using a {@link AddressableLEDBufferView#reversed() reversed view} of the overall buffer for
|
||||
* that segment rather than reversing patterns.
|
||||
*
|
||||
* @return the reverse pattern
|
||||
* @see AddressableLEDBufferView#reversed()
|
||||
*/
|
||||
default LEDPattern reversed() {
|
||||
return mapIndex((length, index) -> length - 1 - index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that plays this one, but offset by a certain number of LEDs. The offset
|
||||
* pattern will wrap around, if necessary.
|
||||
*
|
||||
* @param offset how many LEDs to offset by
|
||||
* @return the offset pattern
|
||||
*/
|
||||
default LEDPattern offsetBy(int offset) {
|
||||
return mapIndex((length, index) -> Math.floorMod(index + offset, length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that plays this one scrolling up the buffer. The velocity controls how fast
|
||||
* the pattern returns back to its original position, and is in terms of the length of the LED
|
||||
* strip; scrolling across a segment that is 10 LEDs long will travel twice as fast as on a
|
||||
* segment that's only 5 LEDs long (assuming equal LED density on both segments).
|
||||
*
|
||||
* <p>For example, scrolling a pattern by one quarter of any LED strip's length per second,
|
||||
* regardless of the total number of LEDs on that strip:
|
||||
*
|
||||
* <pre>
|
||||
* LEDPattern rainbow = LEDPattern.rainbow(255, 255);
|
||||
* LEDPattern scrollingRainbow = rainbow.scrollAtRelativeSpeed(Percent.per(Second).of(25));
|
||||
* </pre>
|
||||
*
|
||||
* @param velocity how fast the pattern should move, in terms of how long it takes to do a full
|
||||
* scroll along the length of LEDs and return back to the starting position
|
||||
* @return the scrolling pattern
|
||||
*/
|
||||
default LEDPattern scrollAtRelativeSpeed(Frequency velocity) {
|
||||
final double periodMicros = velocity.asPeriod().in(Microseconds);
|
||||
|
||||
return mapIndex(
|
||||
(bufLen, index) -> {
|
||||
long now = RobotController.getTime();
|
||||
|
||||
// index should move by (buf.length) / (period)
|
||||
double t = (now % (long) periodMicros) / periodMicros;
|
||||
int offset = (int) (t * bufLen);
|
||||
|
||||
return Math.floorMod(index + offset, bufLen);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that plays this one scrolling up an LED strip. A negative velocity makes the
|
||||
* pattern play in reverse.
|
||||
*
|
||||
* <p>For example, scrolling a pattern at 4 inches per second along an LED strip with 60 LEDs per
|
||||
* meter:
|
||||
*
|
||||
* <pre>
|
||||
* // LEDs per meter, a known value taken from the spec sheet of our particular LED strip
|
||||
* Distance LED_SPACING = Meters.of(1.0 / 60);
|
||||
*
|
||||
* LEDPattern rainbow = LEDPattern.rainbow();
|
||||
* LEDPattern scrollingRainbow =
|
||||
* rainbow.scrollAtAbsoluteSpeed(InchesPerSecond.of(4), LED_SPACING);
|
||||
* </pre>
|
||||
*
|
||||
* <p>Note that this pattern will scroll <i>faster</i> if applied to a less dense LED strip (such
|
||||
* as 30 LEDs per meter), or <i>slower</i> if applied to a denser LED strip (such as 120 or 144
|
||||
* LEDs per meter).
|
||||
*
|
||||
* @param velocity how fast the pattern should move along a physical LED strip
|
||||
* @param ledSpacing the distance between adjacent LEDs on the physical LED strip
|
||||
* @return the scrolling pattern
|
||||
*/
|
||||
default LEDPattern scrollAtAbsoluteSpeed(LinearVelocity velocity, Distance ledSpacing) {
|
||||
// eg velocity = 10 m/s, spacing = 0.01m
|
||||
// meters per micro = 1e-5 m/us
|
||||
// micros per LED = 1e-2 m / (1e-5 m/us) = 1e-3 us
|
||||
|
||||
var metersPerMicro = velocity.in(Meters.per(Microsecond));
|
||||
var microsPerLED = (int) (ledSpacing.in(Meters) / metersPerMicro);
|
||||
|
||||
return mapIndex(
|
||||
(bufLen, index) -> {
|
||||
long now = RobotController.getTime();
|
||||
|
||||
// every step in time that's a multiple of microsPerLED will increment the offset by 1
|
||||
var offset = (int) (now / microsPerLED);
|
||||
|
||||
// floorMod so if the offset is negative, we still get positive outputs
|
||||
return Math.floorMod(index + offset, bufLen);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that switches between playing this pattern and turning the entire LED strip
|
||||
* off.
|
||||
*
|
||||
* @param onTime how long the pattern should play for, per cycle
|
||||
* @param offTime how long the pattern should be turned off for, per cycle
|
||||
* @return the blinking pattern
|
||||
*/
|
||||
default LEDPattern blink(Time onTime, Time offTime) {
|
||||
final long totalTimeMicros = (long) (onTime.in(Microseconds) + offTime.in(Microseconds));
|
||||
final long onTimeMicros = (long) onTime.in(Microseconds);
|
||||
|
||||
return (reader, writer) -> {
|
||||
if (RobotController.getTime() % totalTimeMicros < onTimeMicros) {
|
||||
applyTo(reader, writer);
|
||||
} else {
|
||||
kOff.applyTo(reader, writer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #blink(Time, Time) blink(onTime, offTime)}, but where the "off" time is exactly
|
||||
* equal to the "on" time.
|
||||
*
|
||||
* @param onTime how long the pattern should play for (and be turned off for), per cycle
|
||||
* @return the blinking pattern
|
||||
*/
|
||||
default LEDPattern blink(Time onTime) {
|
||||
return blink(onTime, onTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that blinks this one on and off in sync with a true/false signal. The pattern
|
||||
* will play while the signal outputs {@code true}, and will turn off while the signal outputs
|
||||
* {@code false}.
|
||||
*
|
||||
* @param signal the signal to synchronize with
|
||||
* @return the blinking pattern
|
||||
*/
|
||||
default LEDPattern synchronizedBlink(BooleanSupplier signal) {
|
||||
return (reader, writer) -> {
|
||||
if (signal.getAsBoolean()) {
|
||||
applyTo(reader, writer);
|
||||
} else {
|
||||
kOff.applyTo(reader, writer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that brightens and dims this one over time. Brightness follows a sinusoidal
|
||||
* pattern.
|
||||
*
|
||||
* @param period how fast the breathing pattern should complete a single cycle
|
||||
* @return the breathing pattern
|
||||
*/
|
||||
default LEDPattern breathe(Time period) {
|
||||
final long periodMicros = (long) period.in(Microseconds);
|
||||
|
||||
return (reader, writer) -> {
|
||||
applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
// How far we are in the cycle, in the range [0, 1)
|
||||
double t = (RobotController.getTime() % periodMicros) / (double) periodMicros;
|
||||
double phase = t * 2 * Math.PI;
|
||||
|
||||
// Apply the cosine function and shift its output from [-1, 1] to [0, 1]
|
||||
// Use cosine so the period starts at 100% brightness
|
||||
double dim = (Math.cos(phase) + 1) / 2.0;
|
||||
|
||||
int output = Color.lerpRGB(0, 0, 0, r, g, b, dim);
|
||||
|
||||
writer.setRGB(
|
||||
i,
|
||||
Color.unpackRGB(output, Color.RGBChannel.kRed),
|
||||
Color.unpackRGB(output, Color.RGBChannel.kGreen),
|
||||
Color.unpackRGB(output, Color.RGBChannel.kBlue));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that plays this pattern overlaid on another. Anywhere this pattern sets an
|
||||
* LED to off (or {@link Color#kBlack}), the base pattern will be displayed instead.
|
||||
*
|
||||
* @param base the base pattern to overlay on top of
|
||||
* @return the combined overlay pattern
|
||||
*/
|
||||
default LEDPattern overlayOn(LEDPattern base) {
|
||||
return (reader, writer) -> {
|
||||
// write the base pattern down first...
|
||||
base.applyTo(reader, writer);
|
||||
|
||||
// ... then, overwrite with the illuminated LEDs from the overlay
|
||||
applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
if (r != 0 || g != 0 || b != 0) {
|
||||
writer.setRGB(i, r, g, b);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that displays outputs as a combination of this pattern and another. Color
|
||||
* values are calculated as the average color of both patterns; if both patterns set the same LED
|
||||
* to the same color, then it is set to that color, but if one pattern sets to one color and the
|
||||
* other pattern sets it to off, then it will show the color of the first pattern but at
|
||||
* approximately half brightness. This is different from {@link #overlayOn}, which will show the
|
||||
* base pattern at full brightness if the overlay is set to off at that position.
|
||||
*
|
||||
* @param other the pattern to blend with
|
||||
* @return the blended pattern
|
||||
*/
|
||||
default LEDPattern blend(LEDPattern other) {
|
||||
return (reader, writer) -> {
|
||||
applyTo(reader, writer);
|
||||
|
||||
other.applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
int blendedRGB =
|
||||
Color.lerpRGB(
|
||||
reader.getRed(i), reader.getGreen(i), reader.getBlue(i), r, g, b, 0.5);
|
||||
|
||||
writer.setRGB(
|
||||
i,
|
||||
Color.unpackRGB(blendedRGB, Color.RGBChannel.kRed),
|
||||
Color.unpackRGB(blendedRGB, Color.RGBChannel.kGreen),
|
||||
Color.unpackRGB(blendedRGB, Color.RGBChannel.kBlue));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #blend(LEDPattern)}, but performs a bitwise mask on each color channel rather
|
||||
* than averaging the colors for each LED. This can be helpful for displaying only a portion of
|
||||
* the base pattern by applying a mask that sets the desired area to white, and all other areas to
|
||||
* black. However, it can also be used to display only certain color channels or hues; for
|
||||
* example, masking with {@code LEDPattern.color(Color.kRed)} will turn off the green and blue
|
||||
* channels on the output pattern, leaving only the red LEDs to be illuminated.
|
||||
*
|
||||
* @param mask the mask to apply
|
||||
* @return the masked pattern
|
||||
*/
|
||||
default LEDPattern mask(LEDPattern mask) {
|
||||
return (reader, writer) -> {
|
||||
// Apply the current pattern down as normal...
|
||||
applyTo(reader, writer);
|
||||
|
||||
mask.applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
// ... then perform a bitwise AND operation on each channel to apply the mask
|
||||
writer.setRGB(i, r & reader.getRed(i), g & reader.getGreen(i), b & reader.getBlue(i));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that plays this one, but at a different brightness. Brightness multipliers
|
||||
* are applied per-channel in the RGB space; no HSL or HSV conversions are applied. Multipliers
|
||||
* are also uncapped, which may result in the original colors washing out and appearing less
|
||||
* saturated or even just a bright white.
|
||||
*
|
||||
* <p>This method is predominantly intended for dimming LEDs to avoid painfully bright or
|
||||
* distracting patterns from playing (apologies to the 2024 NE Greater Boston field staff).
|
||||
*
|
||||
* <p>For example, dimming can be done simply by adding a call to `atBrightness` at the end of a
|
||||
* pattern:
|
||||
*
|
||||
* <pre>
|
||||
* // Solid red, but at 50% brightness
|
||||
* LEDPattern.solid(Color.kRed).atBrightness(Percent.of(50));
|
||||
*
|
||||
* // Solid white, but at only 10% (i.e. ~0.5V)
|
||||
* LEDPattern.solid(Color.kWhite).atBrightness(Percent.of(10));
|
||||
* </pre>
|
||||
*
|
||||
* @param relativeBrightness the multiplier to apply to all channels to modify brightness
|
||||
* @return the input pattern, displayed at
|
||||
*/
|
||||
default LEDPattern atBrightness(Dimensionless relativeBrightness) {
|
||||
double multiplier = relativeBrightness.in(Value);
|
||||
|
||||
return (reader, writer) -> {
|
||||
applyTo(
|
||||
reader,
|
||||
(i, r, g, b) -> {
|
||||
// Clamp RGB values to keep them in the range [0, 255].
|
||||
// Otherwise, the casts to byte would result in values like 256 wrapping to 0
|
||||
|
||||
writer.setRGB(
|
||||
i,
|
||||
(int) Math.clamp(r * multiplier, 0, 255),
|
||||
(int) Math.clamp(g * multiplier, 0, 255),
|
||||
(int) Math.clamp(b * multiplier, 0, 255));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** A pattern that turns off all LEDs. */
|
||||
LEDPattern kOff = solid(Color.kBlack);
|
||||
|
||||
/**
|
||||
* Creates a pattern that displays a single static color along the entire length of the LED strip.
|
||||
*
|
||||
* @param color the color to display
|
||||
* @return the pattern
|
||||
*/
|
||||
static LEDPattern solid(Color color) {
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
for (int led = 0; led < bufLen; led++) {
|
||||
writer.setLED(led, color);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that works as a mask layer for {@link #mask(LEDPattern)} that illuminates
|
||||
* only the portion of the LED strip corresponding with some progress. The mask pattern will start
|
||||
* from the base and set LEDs to white at a proportion equal to the progress returned by the
|
||||
* function. Some usages for this could be for displaying progress of a flywheel to its target
|
||||
* velocity, progress of a complex autonomous sequence, or the height of an elevator.
|
||||
*
|
||||
* <p>For example, creating a mask for displaying a red-to-blue gradient, starting from the red
|
||||
* end, based on where an elevator is in its range of travel.
|
||||
*
|
||||
* <pre>
|
||||
* LEDPattern basePattern = gradient(Color.kRed, Color.kBlue);
|
||||
* LEDPattern progressPattern =
|
||||
* basePattern.mask(progressMaskLayer(() -> elevator.getHeight() / elevator.maxHeight());
|
||||
* </pre>
|
||||
*
|
||||
* @param progressSupplier the function to call to determine the progress. This should return
|
||||
* values in the range [0, 1]; any values outside that range will be clamped.
|
||||
* @return the mask pattern
|
||||
*/
|
||||
static LEDPattern progressMaskLayer(DoubleSupplier progressSupplier) {
|
||||
return (reader, writer) -> {
|
||||
double progress = Math.clamp(progressSupplier.getAsDouble(), 0, 1);
|
||||
|
||||
int bufLen = reader.getLength();
|
||||
int max = (int) (bufLen * progress);
|
||||
|
||||
for (int led = 0; led < max; led++) {
|
||||
writer.setLED(led, Color.kWhite);
|
||||
}
|
||||
|
||||
for (int led = max; led < bufLen; led++) {
|
||||
writer.setLED(led, Color.kBlack);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a set of colors in steps across the length of the LED strip. No interpolation is done
|
||||
* between colors. Colors are specified by the first LED on the strip to show that color. The last
|
||||
* color in the map will be displayed all the way to the end of the strip. LEDs positioned before
|
||||
* the first specified step will be turned off (you can think of this as if there's a 0 -> black
|
||||
* step by default)
|
||||
*
|
||||
* <pre>
|
||||
* // Display red from 0-33%, white from 33% - 67%, and blue from 67% to 100%
|
||||
* steps(Map.of(0.00, Color.kRed, 0.33, Color.kWhite, 0.67, Color.kBlue))
|
||||
*
|
||||
* // Half off, half on
|
||||
* steps(Map.of(0.5, Color.kWhite))
|
||||
* </pre>
|
||||
*
|
||||
* @param steps a map of progress to the color to start displaying at that position along the LED
|
||||
* strip
|
||||
* @return a motionless step pattern
|
||||
*/
|
||||
static LEDPattern steps(Map<? extends Number, Color> steps) {
|
||||
if (steps.isEmpty()) {
|
||||
// no colors specified
|
||||
DriverStation.reportWarning("Creating LED steps with no colors!", false);
|
||||
return kOff;
|
||||
}
|
||||
|
||||
if (steps.size() == 1 && steps.keySet().iterator().next().doubleValue() == 0) {
|
||||
// only one color specified, just show a static color
|
||||
DriverStation.reportWarning("Creating LED steps with only one color!", false);
|
||||
return solid(steps.values().iterator().next());
|
||||
}
|
||||
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
|
||||
// precompute relevant positions for this buffer so we don't need to do a check
|
||||
// on every single LED index
|
||||
var stopPositions = new LongToObjectHashMap<Color>();
|
||||
steps.forEach(
|
||||
(progress, color) -> {
|
||||
stopPositions.put((int) Math.floor(progress.doubleValue() * bufLen), color);
|
||||
});
|
||||
|
||||
Color currentColor = Color.kBlack;
|
||||
for (int led = 0; led < bufLen; led++) {
|
||||
currentColor = Objects.requireNonNullElse(stopPositions.get(led), currentColor);
|
||||
|
||||
writer.setLED(led, currentColor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Types of gradients. */
|
||||
enum GradientType {
|
||||
/**
|
||||
* A continuous gradient, where the gradient wraps around to allow for seamless scrolling
|
||||
* effects.
|
||||
*/
|
||||
kContinuous,
|
||||
|
||||
/**
|
||||
* A discontinuous gradient, where the first pixel is set to the first color of the gradient and
|
||||
* the final pixel is set to the last color of the gradient. There is no wrapping effect, so
|
||||
* scrolling effects will display an obvious seam.
|
||||
*/
|
||||
kDiscontinuous
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pattern that displays a non-animated gradient of colors across the entire length of
|
||||
* the LED strip. Colors are evenly distributed along the full length of the LED strip. The
|
||||
* gradient type is configured with the {@code type} parameter, allowing the gradient to be either
|
||||
* continuous (no seams, good for scrolling effects) or discontinuous (a clear seam is visible,
|
||||
* but the gradient applies to the full length of the LED strip without needing to use some space
|
||||
* for wrapping).
|
||||
*
|
||||
* @param type the type of gradient (continuous or discontinuous)
|
||||
* @param colors the colors to display in the gradient
|
||||
* @return a motionless gradient pattern
|
||||
*/
|
||||
static LEDPattern gradient(GradientType type, Color... colors) {
|
||||
if (colors.length == 0) {
|
||||
// Nothing to display
|
||||
DriverStation.reportWarning("Creating a gradient with no colors!", false);
|
||||
return kOff;
|
||||
}
|
||||
|
||||
if (colors.length == 1) {
|
||||
// No gradients with one color
|
||||
DriverStation.reportWarning("Creating a gradient with only one color!", false);
|
||||
return solid(colors[0]);
|
||||
}
|
||||
|
||||
final int numSegments = colors.length;
|
||||
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
int ledsPerSegment =
|
||||
switch (type) {
|
||||
case kContinuous -> bufLen / numSegments;
|
||||
case kDiscontinuous -> (bufLen - 1) / (numSegments - 1);
|
||||
};
|
||||
|
||||
for (int led = 0; led < bufLen; led++) {
|
||||
int colorIndex = (led / ledsPerSegment) % numSegments;
|
||||
int nextColorIndex = (colorIndex + 1) % numSegments;
|
||||
double t = (led / (double) ledsPerSegment) % 1;
|
||||
|
||||
Color color = colors[colorIndex];
|
||||
Color nextColor = colors[nextColorIndex];
|
||||
int gradientColor =
|
||||
Color.lerpRGB(
|
||||
color.red,
|
||||
color.green,
|
||||
color.blue,
|
||||
nextColor.red,
|
||||
nextColor.green,
|
||||
nextColor.blue,
|
||||
t);
|
||||
|
||||
writer.setRGB(
|
||||
led,
|
||||
Color.unpackRGB(gradientColor, Color.RGBChannel.kRed),
|
||||
Color.unpackRGB(gradientColor, Color.RGBChannel.kGreen),
|
||||
Color.unpackRGB(gradientColor, Color.RGBChannel.kBlue));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an LED pattern that displays a rainbow across the color wheel. The rainbow pattern will
|
||||
* stretch across the entire length of the LED strip.
|
||||
*
|
||||
* @param saturation the saturation of the HSV colors, in [0, 255]
|
||||
* @param value the value of the HSV colors, in [0, 255]
|
||||
* @return the rainbow pattern
|
||||
*/
|
||||
static LEDPattern rainbow(int saturation, int value) {
|
||||
return (reader, writer) -> {
|
||||
int bufLen = reader.getLength();
|
||||
for (int i = 0; i < bufLen; i++) {
|
||||
int hue = ((i * 180) / bufLen) % 180;
|
||||
writer.setHSV(i, hue, saturation, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
99
wpilibj/src/main/java/org/wpilib/hardware/led/LEDReader.java
Normal file
99
wpilibj/src/main/java/org/wpilib/hardware/led/LEDReader.java
Normal file
@@ -0,0 +1,99 @@
|
||||
// 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.wpilibj.util.Color;
|
||||
import edu.wpi.first.wpilibj.util.Color8Bit;
|
||||
|
||||
/** Generic interface for reading data from an LED buffer. */
|
||||
public interface LEDReader {
|
||||
/**
|
||||
* Gets the length of the buffer.
|
||||
*
|
||||
* @return the buffer length
|
||||
*/
|
||||
int getLength();
|
||||
|
||||
/**
|
||||
* Gets the most recently written color for a particular LED in the buffer.
|
||||
*
|
||||
* @param index the index of the LED
|
||||
* @return the LED color
|
||||
* @throws IndexOutOfBoundsException if the index is negative or greater than {@link #getLength()}
|
||||
*/
|
||||
default Color getLED(int index) {
|
||||
return new Color(getRed(index) / 255.0, getGreen(index) / 255.0, getBlue(index) / 255.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recently written color for a particular LED in the buffer.
|
||||
*
|
||||
* @param index the index of the LED
|
||||
* @return the LED color
|
||||
* @throws IndexOutOfBoundsException if the index is negative or greater than {@link #getLength()}
|
||||
*/
|
||||
default Color8Bit getLED8Bit(int index) {
|
||||
return new Color8Bit(getRed(index), getGreen(index), getBlue(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the red channel of the color at the specified index.
|
||||
*
|
||||
* @param index the index of the LED to read
|
||||
* @return the value of the red channel, from [0, 255]
|
||||
*/
|
||||
int getRed(int index);
|
||||
|
||||
/**
|
||||
* Gets the green channel of the color at the specified index.
|
||||
*
|
||||
* @param index the index of the LED to read
|
||||
* @return the value of the green channel, from [0, 255]
|
||||
*/
|
||||
int getGreen(int index);
|
||||
|
||||
/**
|
||||
* Gets the blue channel of the color at the specified index.
|
||||
*
|
||||
* @param index the index of the LED to read
|
||||
* @return the value of the blue channel, from [0, 255]
|
||||
*/
|
||||
int getBlue(int index);
|
||||
|
||||
/**
|
||||
* A functional interface that allows for iteration over an LED buffer without manually writing an
|
||||
* indexed for-loop.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface IndexedColorIterator {
|
||||
/**
|
||||
* Accepts an index of an LED in the buffer and the red, green, and blue components of the
|
||||
* currently stored color for that LED.
|
||||
*
|
||||
* @param index the index of the LED in the buffer that the red, green, and blue channels
|
||||
* corresponds to
|
||||
* @param r the value of the red channel of the color currently in the buffer at index {@code i}
|
||||
* @param g the value of the green channel of the color currently in the buffer at index {@code
|
||||
* i}
|
||||
* @param b the value of the blue channel of the color currently in the buffer at index {@code
|
||||
* i}
|
||||
*/
|
||||
void accept(int index, int r, int g, int b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the LEDs in the buffer, starting from index 0. The iterator function is passed
|
||||
* the current index of iteration, along with the values for the red, green, and blue components
|
||||
* of the color written to the LED at that index.
|
||||
*
|
||||
* @param iterator the iterator function to call for each LED in the buffer.
|
||||
*/
|
||||
default void forEach(IndexedColorIterator iterator) {
|
||||
int bufLen = getLength();
|
||||
for (int i = 0; i < bufLen; i++) {
|
||||
iterator.accept(i, getRed(i), getGreen(i), getBlue(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
65
wpilibj/src/main/java/org/wpilib/hardware/led/LEDWriter.java
Normal file
65
wpilibj/src/main/java/org/wpilib/hardware/led/LEDWriter.java
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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.wpilibj.util.Color;
|
||||
import edu.wpi.first.wpilibj.util.Color8Bit;
|
||||
|
||||
/** Generic interface for writing data to an LED buffer. */
|
||||
@FunctionalInterface
|
||||
public interface LEDWriter {
|
||||
/**
|
||||
* Sets the RGB value for an LED at a specific index on a LED buffer.
|
||||
*
|
||||
* @param index the index of the LED to write to
|
||||
* @param r the value of the red channel, in [0, 255]
|
||||
* @param g the value of the green channel, in [0, 255]
|
||||
* @param b the value of the blue channel, in [0, 255]
|
||||
*/
|
||||
void setRGB(int index, int r, int g, int b);
|
||||
|
||||
/**
|
||||
* Sets a specific led in the buffer.
|
||||
*
|
||||
* @param index the index to write
|
||||
* @param h the h value [0-180)
|
||||
* @param s the s value [0-255]
|
||||
* @param v the v value [0-255]
|
||||
*/
|
||||
default void setHSV(int index, int h, int s, int v) {
|
||||
if (s == 0) {
|
||||
setRGB(index, v, v, v);
|
||||
return;
|
||||
}
|
||||
|
||||
int packedRGB = Color.hsvToRgb(h, s, v);
|
||||
|
||||
setRGB(
|
||||
index,
|
||||
Color.unpackRGB(packedRGB, Color.RGBChannel.kRed),
|
||||
Color.unpackRGB(packedRGB, Color.RGBChannel.kGreen),
|
||||
Color.unpackRGB(packedRGB, Color.RGBChannel.kBlue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RGB value for an LED at a specific index on a LED buffer.
|
||||
*
|
||||
* @param index the index of the LED to write to
|
||||
* @param color the color to set
|
||||
*/
|
||||
default void setLED(int index, Color color) {
|
||||
setRGB(index, (int) (color.red * 255), (int) (color.green * 255), (int) (color.blue * 255));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RGB value for an LED at a specific index on a LED buffer.
|
||||
*
|
||||
* @param index the index of the LED to write to
|
||||
* @param color the color to set
|
||||
*/
|
||||
default void setLED(int index, Color8Bit color) {
|
||||
setRGB(index, color.red, color.green, color.blue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.motorcontrol;
|
||||
|
||||
import static edu.wpi.first.units.Units.Volts;
|
||||
|
||||
import edu.wpi.first.units.measure.Voltage;
|
||||
import edu.wpi.first.wpilibj.RobotController;
|
||||
|
||||
/** Interface for motor controlling devices. */
|
||||
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.
|
||||
*/
|
||||
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, in Volts.
|
||||
*/
|
||||
default void setVoltage(double outputVolts) {
|
||||
set(outputVolts / RobotController.getBatteryVoltage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 outputVoltage The voltage to output.
|
||||
*/
|
||||
default void setVoltage(Voltage outputVoltage) {
|
||||
setVoltage(outputVoltage.in(Volts));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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.motorcontrol;
|
||||
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Allows multiple {@link MotorController} objects to be linked together.
|
||||
*
|
||||
* @deprecated Use {@link PWMMotorController#addFollower(PWMMotorController)} or if using CAN motor
|
||||
* controllers, use their method of following.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(forRemoval = true, since = "2024")
|
||||
public class MotorControllerGroup implements MotorController, Sendable, AutoCloseable {
|
||||
private boolean m_isInverted;
|
||||
private final MotorController[] m_motorControllers;
|
||||
private static int instances;
|
||||
|
||||
/**
|
||||
* Create a new MotorControllerGroup with the provided MotorControllers.
|
||||
*
|
||||
* @param motorController The first MotorController to add
|
||||
* @param motorControllers The MotorControllers to add
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public MotorControllerGroup(
|
||||
MotorController motorController, MotorController... motorControllers) {
|
||||
m_motorControllers = new MotorController[motorControllers.length + 1];
|
||||
m_motorControllers[0] = motorController;
|
||||
System.arraycopy(motorControllers, 0, m_motorControllers, 1, motorControllers.length);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MotorControllerGroup with the provided MotorControllers.
|
||||
*
|
||||
* @param motorControllers The MotorControllers to add.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public MotorControllerGroup(MotorController[] motorControllers) {
|
||||
m_motorControllers = Arrays.copyOf(motorControllers, motorControllers.length);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
for (MotorController controller : m_motorControllers) {
|
||||
SendableRegistry.addChild(this, controller);
|
||||
}
|
||||
instances++;
|
||||
SendableRegistry.add(this, "MotorControllerGroup", instances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(double speed) {
|
||||
for (MotorController motorController : m_motorControllers) {
|
||||
motorController.set(m_isInverted ? -speed : speed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVoltage(double outputVolts) {
|
||||
for (MotorController motorController : m_motorControllers) {
|
||||
motorController.setVoltage(m_isInverted ? -outputVolts : outputVolts);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double get() {
|
||||
if (m_motorControllers.length > 0) {
|
||||
return m_motorControllers[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 (MotorController motorController : m_motorControllers) {
|
||||
motorController.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopMotor() {
|
||||
for (MotorController motorController : m_motorControllers) {
|
||||
motorController.stopMotor();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Motor Controller");
|
||||
builder.setActuator(true);
|
||||
builder.addDoubleProperty("Value", this::get, this::set);
|
||||
}
|
||||
}
|
||||
198
wpilibj/src/main/java/org/wpilib/hardware/motor/MotorSafety.java
Normal file
198
wpilibj/src/main/java/org/wpilib/hardware/motor/MotorSafety.java
Normal file
@@ -0,0 +1,198 @@
|
||||
// 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.hal.ControlWord;
|
||||
import edu.wpi.first.hal.DriverStationJNI;
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The Motor Safety feature acts as a watchdog timer for an individual motor. It operates by
|
||||
* maintaining a timer that tracks how long it has been since the feed() method has been called for
|
||||
* that actuator. Code in the Driver Station class initiates a comparison of these timers to the
|
||||
* timeout values for any actuator with safety enabled every 5 received packets (100ms nominal).
|
||||
*
|
||||
* <p>The subclass should call feed() whenever the motor value is updated.
|
||||
*/
|
||||
public abstract class MotorSafety {
|
||||
private static final double kDefaultSafetyExpiration = 0.1;
|
||||
|
||||
private double m_expiration = kDefaultSafetyExpiration;
|
||||
private boolean m_enabled;
|
||||
private double m_stopTime = Timer.getFPGATimestamp();
|
||||
private final Object m_thisMutex = new Object();
|
||||
private static final Set<MotorSafety> m_instanceList = new LinkedHashSet<>();
|
||||
private static final Object m_listMutex = new Object();
|
||||
private static Thread m_safetyThread;
|
||||
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
private static void threadMain() {
|
||||
int event = WPIUtilJNI.createEvent(false, false);
|
||||
DriverStationJNI.provideNewDataEventHandle(event);
|
||||
ControlWord controlWord = new ControlWord();
|
||||
|
||||
int safetyCounter = 0;
|
||||
while (true) {
|
||||
boolean timedOut;
|
||||
try {
|
||||
timedOut = WPIUtilJNI.waitForObjectTimeout(event, 0.1);
|
||||
} catch (InterruptedException e) {
|
||||
DriverStationJNI.removeNewDataEventHandle(event);
|
||||
WPIUtilJNI.destroyEvent(event);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (!timedOut) {
|
||||
DriverStationJNI.getControlWord(controlWord);
|
||||
if (!(controlWord.getEnabled() && controlWord.getDSAttached())) {
|
||||
safetyCounter = 0;
|
||||
}
|
||||
if (++safetyCounter >= 4) {
|
||||
checkMotors();
|
||||
safetyCounter = 0;
|
||||
}
|
||||
} else {
|
||||
safetyCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** MotorSafety constructor. */
|
||||
@SuppressWarnings("this-escape")
|
||||
public MotorSafety() {
|
||||
synchronized (m_listMutex) {
|
||||
m_instanceList.add(this);
|
||||
if (m_safetyThread == null) {
|
||||
m_safetyThread = new Thread(MotorSafety::threadMain, "MotorSafety Thread");
|
||||
m_safetyThread.setDaemon(true);
|
||||
m_safetyThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed the motor safety object.
|
||||
*
|
||||
* <p>Resets the timer on this object that is used to do the timeouts.
|
||||
*/
|
||||
public void feed() {
|
||||
synchronized (m_thisMutex) {
|
||||
m_stopTime = Timer.getFPGATimestamp() + m_expiration;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the expiration time for the corresponding motor safety object.
|
||||
*
|
||||
* @param expirationTime The timeout value in seconds.
|
||||
*/
|
||||
public void setExpiration(double expirationTime) {
|
||||
synchronized (m_thisMutex) {
|
||||
m_expiration = expirationTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the timeout value for the corresponding motor safety object.
|
||||
*
|
||||
* @return the timeout value in seconds.
|
||||
*/
|
||||
public double getExpiration() {
|
||||
synchronized (m_thisMutex) {
|
||||
return m_expiration;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine of the motor is still operating or has timed out.
|
||||
*
|
||||
* @return a true value if the motor is still operating normally and hasn't timed out.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
synchronized (m_thisMutex) {
|
||||
return !m_enabled || m_stopTime > Timer.getFPGATimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this motor has exceeded its timeout. This method is called periodically to determine
|
||||
* if this motor has exceeded its timeout value. If it has, the stop method is called, and the
|
||||
* motor is shut down until its value is updated again.
|
||||
*/
|
||||
public void check() {
|
||||
boolean enabled;
|
||||
double stopTime;
|
||||
|
||||
synchronized (m_thisMutex) {
|
||||
enabled = m_enabled;
|
||||
stopTime = m_stopTime;
|
||||
}
|
||||
|
||||
if (!enabled || RobotState.isDisabled() || RobotState.isTest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stopTime < Timer.getFPGATimestamp()) {
|
||||
DriverStation.reportError(
|
||||
getDescription()
|
||||
+ "... Output not updated often enough. See https://docs.wpilib.org/motorsafety for more information.",
|
||||
false);
|
||||
|
||||
stopMotor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable motor safety for this device.
|
||||
*
|
||||
* <p>Turn on and off the motor safety option for this PWM object.
|
||||
*
|
||||
* @param enabled True if motor safety is enforced for this object
|
||||
*/
|
||||
public void setSafetyEnabled(boolean enabled) {
|
||||
synchronized (m_thisMutex) {
|
||||
m_enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state of the motor safety enabled flag.
|
||||
*
|
||||
* <p>Return if the motor safety is currently enabled for this device.
|
||||
*
|
||||
* @return True if motor safety is enforced for this device
|
||||
*/
|
||||
public boolean isSafetyEnabled() {
|
||||
synchronized (m_thisMutex) {
|
||||
return m_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the motors to see if any have timed out.
|
||||
*
|
||||
* <p>This static method is called periodically to poll all the motors and stop any that have
|
||||
* timed out.
|
||||
*/
|
||||
public static void checkMotors() {
|
||||
synchronized (m_listMutex) {
|
||||
for (MotorSafety elem : m_instanceList) {
|
||||
elem.check();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Called to stop the motor when the timeout expires. */
|
||||
public abstract void stopMotor();
|
||||
|
||||
/**
|
||||
* Returns a description to print when an error occurs.
|
||||
*
|
||||
* @return Description to print when an error occurs.
|
||||
*/
|
||||
public abstract String getDescription();
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
// 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.motorcontrol;
|
||||
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.hal.SimDevice.Direction;
|
||||
import edu.wpi.first.hal.SimDouble;
|
||||
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.MotorSafety;
|
||||
import edu.wpi.first.wpilibj.PWM;
|
||||
import edu.wpi.first.wpilibj.RobotController;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Common base class for all PWM Motor Controllers. */
|
||||
@SuppressWarnings("removal")
|
||||
public abstract class PWMMotorController extends MotorSafety
|
||||
implements MotorController, Sendable, AutoCloseable {
|
||||
private boolean m_isInverted;
|
||||
private final ArrayList<PWMMotorController> m_followers = new ArrayList<>();
|
||||
|
||||
/** PWM instances for motor controller. */
|
||||
protected PWM m_pwm;
|
||||
|
||||
private SimDevice m_simDevice;
|
||||
private SimDouble m_simSpeed;
|
||||
|
||||
private boolean m_eliminateDeadband;
|
||||
private int m_minPwm;
|
||||
private int m_deadbandMinPwm;
|
||||
private int m_centerPwm;
|
||||
private int m_deadbandMaxPwm;
|
||||
private int m_maxPwm;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name to use for SendableRegistry
|
||||
* @param channel The PWM channel that the controller is attached to. 0-9 are on-board, 10-19 are
|
||||
* on the MXP port
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
protected PWMMotorController(final String name, final int channel) {
|
||||
m_pwm = new PWM(channel, false);
|
||||
SendableRegistry.add(this, name, channel);
|
||||
|
||||
m_simDevice = SimDevice.create("PWMMotorController", channel);
|
||||
if (m_simDevice != null) {
|
||||
m_simSpeed = m_simDevice.createDouble("Speed", Direction.kOutput, 0.0);
|
||||
m_pwm.setSimDevice(m_simDevice);
|
||||
}
|
||||
}
|
||||
|
||||
/** Free the resource associated with the PWM channel and set the value to 0. */
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
m_pwm.close();
|
||||
|
||||
if (m_simDevice != null) {
|
||||
m_simDevice.close();
|
||||
m_simDevice = null;
|
||||
m_simSpeed = null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getMinPositivePwm() {
|
||||
if (m_eliminateDeadband) {
|
||||
return m_deadbandMaxPwm;
|
||||
} else {
|
||||
return m_centerPwm + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxNegativePwm() {
|
||||
if (m_eliminateDeadband) {
|
||||
return m_deadbandMinPwm;
|
||||
} else {
|
||||
return m_centerPwm - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int getPositiveScaleFactor() {
|
||||
return m_maxPwm - getMinPositivePwm();
|
||||
}
|
||||
|
||||
private int getNegativeScaleFactor() {
|
||||
return getMaxNegativePwm() - m_minPwm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a speed from -1 to 1, and outputs it in the microsecond format.
|
||||
*
|
||||
* @param speed the speed to output
|
||||
*/
|
||||
protected final void setSpeed(double speed) {
|
||||
if (Double.isFinite(speed)) {
|
||||
speed = Math.clamp(speed, -1.0, 1.0);
|
||||
} else {
|
||||
speed = 0.0;
|
||||
}
|
||||
|
||||
if (m_simSpeed != null) {
|
||||
m_simSpeed.set(speed);
|
||||
}
|
||||
|
||||
int rawValue;
|
||||
if (speed == 0.0) {
|
||||
rawValue = m_centerPwm;
|
||||
} else if (speed > 0.0) {
|
||||
rawValue = (int) Math.round(speed * getPositiveScaleFactor()) + getMinPositivePwm();
|
||||
} else {
|
||||
rawValue = (int) Math.round(speed * getNegativeScaleFactor()) + getMaxNegativePwm();
|
||||
}
|
||||
|
||||
m_pwm.setPulseTimeMicroseconds(rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the speed from -1 to 1, from the currently set pulse time.
|
||||
*
|
||||
* @return motor controller speed
|
||||
*/
|
||||
protected final double getSpeed() {
|
||||
int rawValue = m_pwm.getPulseTimeMicroseconds();
|
||||
|
||||
if (rawValue == 0) {
|
||||
return 0.0;
|
||||
} else if (rawValue > m_maxPwm) {
|
||||
return 1.0;
|
||||
} else if (rawValue < m_minPwm) {
|
||||
return -1.0;
|
||||
} else if (rawValue > getMinPositivePwm()) {
|
||||
return (rawValue - getMinPositivePwm()) / (double) getPositiveScaleFactor();
|
||||
} else if (rawValue < getMaxNegativePwm()) {
|
||||
return (rawValue - getMaxNegativePwm()) / (double) getNegativeScaleFactor();
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bounds in microseconds for the controller.
|
||||
*
|
||||
* @param maxPwm maximum
|
||||
* @param deadbandMaxPwm deadband max
|
||||
* @param centerPwm center
|
||||
* @param deadbandMinPwm deadmand min
|
||||
* @param minPwm minimum
|
||||
*/
|
||||
protected final void setBoundsMicroseconds(
|
||||
int maxPwm, int deadbandMaxPwm, int centerPwm, int deadbandMinPwm, int minPwm) {
|
||||
m_maxPwm = maxPwm;
|
||||
m_deadbandMaxPwm = deadbandMaxPwm;
|
||||
m_centerPwm = centerPwm;
|
||||
m_deadbandMinPwm = deadbandMinPwm;
|
||||
m_minPwm = minPwm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PWM value.
|
||||
*
|
||||
* <p>The PWM value is set using a range of -1.0 to 1.0, appropriately scaling the value for the
|
||||
* FPGA.
|
||||
*
|
||||
* @param speed The speed value between -1.0 and 1.0 to set.
|
||||
*/
|
||||
@Override
|
||||
public void set(double speed) {
|
||||
if (m_isInverted) {
|
||||
speed = -speed;
|
||||
}
|
||||
setSpeed(speed);
|
||||
|
||||
for (var follower : m_followers) {
|
||||
follower.set(speed);
|
||||
}
|
||||
|
||||
feed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the recently set value of the PWM. This value is affected by the inversion property.
|
||||
*
|
||||
* @return The most recently set value for the PWM between -1.0 and 1.0.
|
||||
*/
|
||||
@Override
|
||||
public double get() {
|
||||
return getSpeed() * (m_isInverted ? -1.0 : 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the voltage output of the motor controller, nominally between -12 V and 12 V.
|
||||
*
|
||||
* @return The voltage of the motor controller, nominally between -12 V and 12 V.
|
||||
*/
|
||||
public double getVoltage() {
|
||||
return get() * RobotController.getBatteryVoltage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInverted(boolean isInverted) {
|
||||
m_isInverted = isInverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getInverted() {
|
||||
return m_isInverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
m_pwm.setDisabled();
|
||||
|
||||
if (m_simSpeed != null) {
|
||||
m_simSpeed.set(0.0);
|
||||
}
|
||||
|
||||
for (var follower : m_followers) {
|
||||
follower.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopMotor() {
|
||||
// Don't use set(0) as that will feed the watch kitty
|
||||
m_pwm.setPulseTimeMicroseconds(0);
|
||||
|
||||
for (var follower : m_followers) {
|
||||
follower.stopMotor();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "PWM " + getChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the backing PWM handle.
|
||||
*
|
||||
* @return The pwm handle.
|
||||
*/
|
||||
public int getPwmHandle() {
|
||||
return m_pwm.getHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PWM channel number.
|
||||
*
|
||||
* @return The channel number.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_pwm.getChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally eliminate the deadband from a motor controller.
|
||||
*
|
||||
* @param eliminateDeadband If true, set the motor curve for the motor controller to eliminate the
|
||||
* deadband in the middle of the range. Otherwise, keep the full range without modifying any
|
||||
* values.
|
||||
*/
|
||||
public void enableDeadbandElimination(boolean eliminateDeadband) {
|
||||
m_eliminateDeadband = eliminateDeadband;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given PWM motor controller follow the output of this one.
|
||||
*
|
||||
* @param follower The motor controller follower.
|
||||
*/
|
||||
public void addFollower(PWMMotorController follower) {
|
||||
m_followers.add(follower);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Motor Controller");
|
||||
builder.setActuator(true);
|
||||
builder.addDoubleProperty("Value", this::get, this::set);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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.hal.util.AllocationException;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class for operating a compressor connected to a pneumatics module. The module will automatically
|
||||
* run in closed loop mode by default whenever a {@link Solenoid} object is created. For most cases,
|
||||
* a Compressor object does not need to be instantiated or used in a robot program. This class is
|
||||
* only required in cases where the robot program needs a more detailed status of the compressor or
|
||||
* to enable/disable closed loop control.
|
||||
*
|
||||
* <p>Note: you cannot operate the compressor directly from this class as doing so would circumvent
|
||||
* the safety provided by using the pressure switch and closed loop control. You can only turn off
|
||||
* closed loop control, thereby stopping the compressor from operating.
|
||||
*/
|
||||
public class Compressor implements Sendable, AutoCloseable {
|
||||
private PneumaticsBase m_module;
|
||||
private PneumaticsModuleType m_moduleType;
|
||||
|
||||
/**
|
||||
* Constructs a compressor for a specified module and type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module The module ID to use.
|
||||
* @param moduleType The module type to use.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public Compressor(int busId, int module, PneumaticsModuleType moduleType) {
|
||||
m_module = PneumaticsBase.getForType(busId, module, moduleType);
|
||||
m_moduleType = moduleType;
|
||||
|
||||
if (!m_module.reserveCompressor()) {
|
||||
m_module.close();
|
||||
throw new AllocationException("Compressor already allocated");
|
||||
}
|
||||
|
||||
m_module.enableCompressorDigital();
|
||||
|
||||
m_module.reportUsage("Compressor", "");
|
||||
SendableRegistry.add(this, "Compressor", module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a compressor for a default module and specified type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param moduleType The module type to use.
|
||||
*/
|
||||
public Compressor(int busId, PneumaticsModuleType moduleType) {
|
||||
this(busId, PneumaticsBase.getDefaultForType(moduleType), moduleType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
m_module.unreserveCompressor();
|
||||
m_module.close();
|
||||
m_module = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compressor is active or not.
|
||||
*
|
||||
* @return true if the compressor is on - otherwise false.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return m_module.getCompressor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the pressure switch.
|
||||
*
|
||||
* @return True if pressure switch indicates that the system is not full, otherwise false.
|
||||
*/
|
||||
public boolean getPressureSwitchValue() {
|
||||
return m_module.getPressureSwitch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current drawn by the compressor.
|
||||
*
|
||||
* @return Current drawn by the compressor in amps.
|
||||
*/
|
||||
public double getCurrent() {
|
||||
return m_module.getCompressorCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* If supported by the device, returns the analog input voltage (on channel 0).
|
||||
*
|
||||
* <p>This function is only supported by the REV PH. On CTRE PCM, this will return 0.
|
||||
*
|
||||
* @return The analog input voltage, in volts.
|
||||
*/
|
||||
public double getAnalogVoltage() {
|
||||
return m_module.getAnalogVoltage(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* If supported by the device, returns the pressure (in PSI) read by the analog pressure sensor
|
||||
* (on channel 0).
|
||||
*
|
||||
* <p>This function is only supported by the REV PH with the REV Analog Pressure Sensor. On CTRE
|
||||
* PCM, this will return 0.
|
||||
*
|
||||
* @return The pressure (in PSI) read by the analog pressure sensor.
|
||||
*/
|
||||
public double getPressure() {
|
||||
return m_module.getPressure(0);
|
||||
}
|
||||
|
||||
/** Disable the compressor. */
|
||||
public void disable() {
|
||||
m_module.disableCompressor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compressor in digital mode using the digital pressure switch. The compressor will
|
||||
* turn on when the pressure switch indicates that the system is not full, and will turn off when
|
||||
* the pressure switch indicates that the system is full.
|
||||
*/
|
||||
public void enableDigital() {
|
||||
m_module.enableCompressorDigital();
|
||||
}
|
||||
|
||||
/**
|
||||
* If supported by the device, enables the compressor in analog mode. This mode uses an analog
|
||||
* pressure sensor connected to analog channel 0 to cycle the compressor. The compressor will turn
|
||||
* on when the pressure drops below {@code minPressure} and will turn off when the pressure
|
||||
* reaches {@code maxPressure}. This mode is only supported by the REV PH with the REV Analog
|
||||
* Pressure Sensor connected to analog channel 0.
|
||||
*
|
||||
* <p>On CTRE PCM, this will enable digital control.
|
||||
*
|
||||
* @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure
|
||||
* drops below this value.
|
||||
* @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure
|
||||
* reaches this value.
|
||||
*/
|
||||
public void enableAnalog(double minPressure, double maxPressure) {
|
||||
m_module.enableCompressorAnalog(minPressure, maxPressure);
|
||||
}
|
||||
|
||||
/**
|
||||
* If supported by the device, enables the compressor in hybrid mode. This mode uses both a
|
||||
* digital pressure switch and an analog pressure sensor connected to analog channel 0 to cycle
|
||||
* the compressor. This mode is only supported by the REV PH with the REV Analog Pressure Sensor
|
||||
* connected to analog channel 0.
|
||||
*
|
||||
* <p>The compressor will turn on when <i>both</i>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The digital pressure switch indicates the system is not full AND
|
||||
* <li>The analog pressure sensor indicates that the pressure in the system is below the
|
||||
* specified minimum pressure.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The compressor will turn off when <i>either</i>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The digital pressure switch is disconnected or indicates that the system is full OR
|
||||
* <li>The pressure detected by the analog sensor is greater than the specified maximum
|
||||
* pressure.
|
||||
* </ul>
|
||||
*
|
||||
* <p>On CTRE PCM, this will enable digital control.
|
||||
*
|
||||
* @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure
|
||||
* drops below this value and the pressure switch indicates that the system is not full.
|
||||
* @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure
|
||||
* reaches this value or the pressure switch is disconnected or indicates that the system is
|
||||
* full.
|
||||
*/
|
||||
public void enableHybrid(double minPressure, double maxPressure) {
|
||||
m_module.enableCompressorHybrid(minPressure, maxPressure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active compressor configuration.
|
||||
*
|
||||
* @return The active compressor configuration.
|
||||
*/
|
||||
public CompressorConfigType getConfigType() {
|
||||
return m_module.getCompressorConfigType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Compressor");
|
||||
builder.addBooleanProperty("Enabled", this::isEnabled, null);
|
||||
builder.addBooleanProperty("Pressure switch", this::getPressureSwitchValue, null);
|
||||
builder.addDoubleProperty("Current (A)", this::getCurrent, null);
|
||||
if (m_moduleType == PneumaticsModuleType.REVPH) { // These are not supported by the CTRE PCM
|
||||
builder.addDoubleProperty("Analog Voltage", this::getAnalogVoltage, null);
|
||||
builder.addDoubleProperty("Pressure (PSI)", this::getPressure, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.hal.REVPHJNI;
|
||||
|
||||
/** Compressor config type. */
|
||||
public enum CompressorConfigType {
|
||||
/** Disabled. */
|
||||
Disabled(REVPHJNI.COMPRESSOR_CONFIG_TYPE_DISABLED),
|
||||
/** Digital. */
|
||||
Digital(REVPHJNI.COMPRESSOR_CONFIG_TYPE_DIGITAL),
|
||||
/** Analog. */
|
||||
Analog(REVPHJNI.COMPRESSOR_CONFIG_TYPE_ANALOG),
|
||||
/** Hybrid. */
|
||||
Hybrid(REVPHJNI.COMPRESSOR_CONFIG_TYPE_HYBRID);
|
||||
|
||||
/** CompressorConfigType value. */
|
||||
public final int value;
|
||||
|
||||
CompressorConfigType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a type from an int value.
|
||||
*
|
||||
* @param value int value
|
||||
* @return type
|
||||
*/
|
||||
public static CompressorConfigType fromValue(int value) {
|
||||
return switch (value) {
|
||||
case REVPHJNI.COMPRESSOR_CONFIG_TYPE_HYBRID -> Hybrid;
|
||||
case REVPHJNI.COMPRESSOR_CONFIG_TYPE_ANALOG -> Analog;
|
||||
case REVPHJNI.COMPRESSOR_CONFIG_TYPE_DIGITAL -> Digital;
|
||||
default -> Disabled;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CompressorConfigType's value.
|
||||
*
|
||||
* @return The CompressorConfigType's value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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.hal.util.AllocationException;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* DoubleSolenoid class for running 2 channels of high voltage Digital Output on the pneumatics
|
||||
* module.
|
||||
*
|
||||
* <p>The DoubleSolenoid class is typically used for pneumatics solenoids that have two positions
|
||||
* controlled by two separate channels.
|
||||
*/
|
||||
public class DoubleSolenoid implements Sendable, AutoCloseable {
|
||||
/** Possible values for a DoubleSolenoid. */
|
||||
public enum Value {
|
||||
/** Off position. */
|
||||
kOff,
|
||||
/** Forward position. */
|
||||
kForward,
|
||||
/** Reverse position. */
|
||||
kReverse
|
||||
}
|
||||
|
||||
private final int m_forwardMask; // The mask for the forward channel.
|
||||
private final int m_reverseMask; // The mask for the reverse channel.
|
||||
private final int m_mask; // The channel mask
|
||||
private PneumaticsBase m_module;
|
||||
private final int m_forwardChannel;
|
||||
private final int m_reverseChannel;
|
||||
|
||||
/**
|
||||
* Constructs a double solenoid for a default module of a specific module type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param moduleType The module type to use.
|
||||
* @param forwardChannel The forward channel on the module to control.
|
||||
* @param reverseChannel The reverse channel on the module to control.
|
||||
*/
|
||||
public DoubleSolenoid(
|
||||
final int busId,
|
||||
final PneumaticsModuleType moduleType,
|
||||
final int forwardChannel,
|
||||
final int reverseChannel) {
|
||||
this(
|
||||
busId,
|
||||
PneumaticsBase.getDefaultForType(moduleType),
|
||||
moduleType,
|
||||
forwardChannel,
|
||||
reverseChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a double solenoid for a specified module of a specific module type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module The module of the solenoid module to use.
|
||||
* @param moduleType The module type to use.
|
||||
* @param forwardChannel The forward channel on the module to control.
|
||||
* @param reverseChannel The reverse channel on the module to control.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.UseTryWithResources", "this-escape"})
|
||||
public DoubleSolenoid(
|
||||
final int busId,
|
||||
final int module,
|
||||
final PneumaticsModuleType moduleType,
|
||||
final int forwardChannel,
|
||||
final int reverseChannel) {
|
||||
m_module = PneumaticsBase.getForType(busId, module, moduleType);
|
||||
boolean allocatedSolenoids = false;
|
||||
boolean successfulCompletion = false;
|
||||
|
||||
m_forwardChannel = forwardChannel;
|
||||
m_reverseChannel = reverseChannel;
|
||||
|
||||
m_forwardMask = 1 << forwardChannel;
|
||||
m_reverseMask = 1 << reverseChannel;
|
||||
m_mask = m_forwardMask | m_reverseMask;
|
||||
|
||||
try {
|
||||
if (!m_module.checkSolenoidChannel(forwardChannel)) {
|
||||
throw new IllegalArgumentException("Channel " + forwardChannel + " out of range");
|
||||
}
|
||||
|
||||
if (!m_module.checkSolenoidChannel(reverseChannel)) {
|
||||
throw new IllegalArgumentException("Channel " + reverseChannel + " out of range");
|
||||
}
|
||||
|
||||
int allocMask = m_module.checkAndReserveSolenoids(m_mask);
|
||||
if (allocMask != 0) {
|
||||
if (allocMask == m_mask) {
|
||||
throw new AllocationException(
|
||||
"Channels " + forwardChannel + " and " + reverseChannel + " already allocated");
|
||||
} else if (allocMask == m_forwardMask) {
|
||||
throw new AllocationException("Channel " + forwardChannel + " already allocated");
|
||||
} else {
|
||||
throw new AllocationException("Channel " + reverseChannel + " already allocated");
|
||||
}
|
||||
}
|
||||
allocatedSolenoids = true;
|
||||
|
||||
m_module.reportUsage(
|
||||
"Solenoid[" + forwardChannel + "," + reverseChannel + "]", "DoubleSolenoid");
|
||||
SendableRegistry.add(this, "DoubleSolenoid", m_module.getModuleNumber(), forwardChannel);
|
||||
successfulCompletion = true;
|
||||
} finally {
|
||||
if (!successfulCompletion) {
|
||||
if (allocatedSolenoids) {
|
||||
m_module.unreserveSolenoids(m_mask);
|
||||
}
|
||||
m_module.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
SendableRegistry.remove(this);
|
||||
m_module.unreserveSolenoids(m_mask);
|
||||
m_module.close();
|
||||
m_module = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a solenoid.
|
||||
*
|
||||
* @param value The value to set (Off, Forward, Reverse)
|
||||
*/
|
||||
public void set(final Value value) {
|
||||
int setValue =
|
||||
switch (value) {
|
||||
case kOff -> 0;
|
||||
case kForward -> m_forwardMask;
|
||||
case kReverse -> m_reverseMask;
|
||||
};
|
||||
|
||||
m_module.setSolenoids(m_mask, setValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the current value of the solenoid.
|
||||
*
|
||||
* @return The current value of the solenoid.
|
||||
*/
|
||||
public Value get() {
|
||||
int values = m_module.getSolenoids();
|
||||
|
||||
if ((values & m_forwardMask) != 0) {
|
||||
return Value.kForward;
|
||||
} else if ((values & m_reverseMask) != 0) {
|
||||
return Value.kReverse;
|
||||
} else {
|
||||
return Value.kOff;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the value of the solenoid.
|
||||
*
|
||||
* <p>If the solenoid is set to forward, it'll be set to reverse. If the solenoid is set to
|
||||
* reverse, it'll be set to forward. If the solenoid is set to off, nothing happens.
|
||||
*/
|
||||
public void toggle() {
|
||||
Value value = get();
|
||||
|
||||
if (value == Value.kForward) {
|
||||
set(Value.kReverse);
|
||||
} else if (value == Value.kReverse) {
|
||||
set(Value.kForward);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forward channel.
|
||||
*
|
||||
* @return the forward channel.
|
||||
*/
|
||||
public int getFwdChannel() {
|
||||
return m_forwardChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reverse channel.
|
||||
*
|
||||
* @return the reverse channel.
|
||||
*/
|
||||
public int getRevChannel() {
|
||||
return m_reverseChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the forward solenoid is Disabled. If a solenoid is shorted, it is added to the
|
||||
* DisabledList and disabled until power cycle, or until faults are cleared.
|
||||
*
|
||||
* @return If solenoid is disabled due to short.
|
||||
*/
|
||||
public boolean isFwdSolenoidDisabled() {
|
||||
return (m_module.getSolenoidDisabledList() & m_forwardMask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the reverse solenoid is Disabled. If a solenoid is shorted, it is added to the
|
||||
* DisabledList and disabled until power cycle, or until faults are cleared.
|
||||
*
|
||||
* @return If solenoid is disabled due to short.
|
||||
*/
|
||||
public boolean isRevSolenoidDisabled() {
|
||||
return (m_module.getSolenoidDisabledList() & m_reverseMask) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Double Solenoid");
|
||||
builder.setActuator(true);
|
||||
builder.addStringProperty(
|
||||
"Value",
|
||||
() -> get().name().substring(1),
|
||||
value -> {
|
||||
if ("Forward".equals(value)) {
|
||||
set(Value.kForward);
|
||||
} else if ("Reverse".equals(value)) {
|
||||
set(Value.kReverse);
|
||||
} else {
|
||||
set(Value.kOff);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.PortsJNI;
|
||||
import edu.wpi.first.hal.REVPHFaults;
|
||||
import edu.wpi.first.hal.REVPHJNI;
|
||||
import edu.wpi.first.hal.REVPHStickyFaults;
|
||||
import edu.wpi.first.hal.REVPHVersion;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Module class for controlling a REV Robotics Pneumatic Hub. */
|
||||
public class PneumaticHub implements PneumaticsBase {
|
||||
private static class DataStore implements AutoCloseable {
|
||||
public final int m_module;
|
||||
public final int m_handle;
|
||||
private final int m_busId;
|
||||
private int m_refCount;
|
||||
private int m_reservedMask;
|
||||
private boolean m_compressorReserved;
|
||||
public final int[] m_oneShotDurMs = new int[PortsJNI.getNumREVPHChannels()];
|
||||
private final Object m_reserveLock = new Object();
|
||||
|
||||
DataStore(int busId, int module) {
|
||||
m_handle = REVPHJNI.initialize(busId, module);
|
||||
m_module = module;
|
||||
m_busId = busId;
|
||||
m_handleMaps[busId].put(module, this);
|
||||
|
||||
final REVPHVersion version = REVPHJNI.getVersion(m_handle);
|
||||
final String fwVersion =
|
||||
version.firmwareMajor + "." + version.firmwareMinor + "." + version.firmwareFix;
|
||||
|
||||
// Check PH firmware version
|
||||
if (version.firmwareMajor > 0 && version.firmwareMajor < 22) {
|
||||
throw new IllegalStateException(
|
||||
"The Pneumatic Hub has firmware version "
|
||||
+ fwVersion
|
||||
+ ", and must be updated to version 2022.0.0 or later "
|
||||
+ "using the REV Hardware Client.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
REVPHJNI.free(m_handle);
|
||||
m_handleMaps[m_busId].remove(m_module);
|
||||
}
|
||||
|
||||
public void addRef() {
|
||||
m_refCount++;
|
||||
}
|
||||
|
||||
public void removeRef() {
|
||||
m_refCount--;
|
||||
if (m_refCount == 0) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static final Map<Integer, DataStore>[] m_handleMaps =
|
||||
(Map<Integer, DataStore>[]) new Map[PortsJNI.getNumCanBuses()];
|
||||
|
||||
private static final Object m_handleLock = new Object();
|
||||
|
||||
private static DataStore getForModule(int busId, int module) {
|
||||
synchronized (m_handleLock) {
|
||||
Map<Integer, DataStore> handleMap = m_handleMaps[busId];
|
||||
if (handleMap == null) {
|
||||
handleMap = new HashMap<>();
|
||||
m_handleMaps[busId] = handleMap;
|
||||
}
|
||||
|
||||
DataStore pcm = handleMap.get(module);
|
||||
if (pcm == null) {
|
||||
pcm = new DataStore(busId, module);
|
||||
}
|
||||
pcm.addRef();
|
||||
return pcm;
|
||||
}
|
||||
}
|
||||
|
||||
private static void freeModule(DataStore store) {
|
||||
synchronized (m_handleLock) {
|
||||
store.removeRef();
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts volts to PSI per the REV Analog Pressure Sensor datasheet. */
|
||||
private static double voltsToPsi(double sensorVoltage, double supplyVoltage) {
|
||||
return 250 * (sensorVoltage / supplyVoltage) - 25;
|
||||
}
|
||||
|
||||
/** Converts PSI to volts per the REV Analog Pressure Sensor datasheet. */
|
||||
private static double psiToVolts(double pressure, double supplyVoltage) {
|
||||
return supplyVoltage * (0.004 * pressure + 0.1);
|
||||
}
|
||||
|
||||
private final DataStore m_dataStore;
|
||||
private final int m_handle;
|
||||
|
||||
/**
|
||||
* Constructs a PneumaticHub with the default ID (1).
|
||||
*
|
||||
* @param busId The bus ID
|
||||
*/
|
||||
public PneumaticHub(int busId) {
|
||||
this(busId, SensorUtil.getDefaultREVPHModule());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a PneumaticHub.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module module number to construct
|
||||
*/
|
||||
public PneumaticHub(int busId, int module) {
|
||||
m_dataStore = getForModule(busId, module);
|
||||
m_handle = m_dataStore.m_handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
freeModule(m_dataStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCompressor() {
|
||||
return REVPHJNI.getCompressor(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressorConfigType getCompressorConfigType() {
|
||||
return CompressorConfigType.fromValue(REVPHJNI.getCompressorConfig(m_handle));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPressureSwitch() {
|
||||
return REVPHJNI.getPressureSwitch(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCompressorCurrent() {
|
||||
return REVPHJNI.getCompressorCurrent(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSolenoids(int mask, int values) {
|
||||
REVPHJNI.setSolenoids(m_handle, mask, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSolenoids() {
|
||||
return REVPHJNI.getSolenoids(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModuleNumber() {
|
||||
return m_dataStore.m_module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireOneShot(int index) {
|
||||
REVPHJNI.fireOneShot(m_handle, index, m_dataStore.m_oneShotDurMs[index]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOneShotDuration(int index, int durMs) {
|
||||
m_dataStore.m_oneShotDurMs[index] = durMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkSolenoidChannel(int channel) {
|
||||
return REVPHJNI.checkSolenoidChannel(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int checkAndReserveSolenoids(int mask) {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
if ((m_dataStore.m_reservedMask & mask) != 0) {
|
||||
return m_dataStore.m_reservedMask & mask;
|
||||
}
|
||||
m_dataStore.m_reservedMask |= mask;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unreserveSolenoids(int mask) {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
m_dataStore.m_reservedMask &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Solenoid makeSolenoid(int channel) {
|
||||
return new Solenoid(m_dataStore.m_module, PneumaticsModuleType.REVPH, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DoubleSolenoid makeDoubleSolenoid(int forwardChannel, int reverseChannel) {
|
||||
return new DoubleSolenoid(
|
||||
m_dataStore.m_module, PneumaticsModuleType.REVPH, forwardChannel, reverseChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Compressor makeCompressor() {
|
||||
return new Compressor(m_dataStore.m_module, PneumaticsModuleType.REVPH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reserveCompressor() {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
if (m_dataStore.m_compressorReserved) {
|
||||
return false;
|
||||
}
|
||||
m_dataStore.m_compressorReserved = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unreserveCompressor() {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
m_dataStore.m_compressorReserved = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSolenoidDisabledList() {
|
||||
return REVPHJNI.getSolenoidDisabledList(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the compressor. The compressor will not turn on until {@link
|
||||
* #enableCompressorDigital()}, {@link #enableCompressorAnalog(double, double)}, or {@link
|
||||
* #enableCompressorHybrid(double, double)} are called.
|
||||
*/
|
||||
@Override
|
||||
public void disableCompressor() {
|
||||
REVPHJNI.setClosedLoopControlDisabled(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCompressorDigital() {
|
||||
REVPHJNI.setClosedLoopControlDigital(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compressor in analog mode. This mode uses an analog pressure sensor connected to
|
||||
* analog channel 0 to cycle the compressor. The compressor will turn on when the pressure drops
|
||||
* below {@code minPressure} and will turn off when the pressure reaches {@code maxPressure}.
|
||||
*
|
||||
* @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure
|
||||
* drops below this value. Range 0-120 PSI.
|
||||
* @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure
|
||||
* reaches this value. Range 0-120 PSI. Must be larger then minPressure.
|
||||
*/
|
||||
@Override
|
||||
public void enableCompressorAnalog(double minPressure, double maxPressure) {
|
||||
if (minPressure >= maxPressure) {
|
||||
throw new IllegalArgumentException("maxPressure must be greater than minPressure");
|
||||
}
|
||||
if (minPressure < 0 || minPressure > 120) {
|
||||
throw new IllegalArgumentException(
|
||||
"minPressure must be between 0 and 120 PSI, got " + minPressure);
|
||||
}
|
||||
if (maxPressure < 0 || maxPressure > 120) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxPressure must be between 0 and 120 PSI, got " + maxPressure);
|
||||
}
|
||||
|
||||
// Send the voltage as it would be if the 5V rail was at exactly 5V.
|
||||
// The firmware will compensate for the real 5V rail voltage, which
|
||||
// can fluctuate somewhat over time.
|
||||
double minAnalogVoltage = psiToVolts(minPressure, 5);
|
||||
double maxAnalogVoltage = psiToVolts(maxPressure, 5);
|
||||
REVPHJNI.setClosedLoopControlAnalog(m_handle, minAnalogVoltage, maxAnalogVoltage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compressor in hybrid mode. This mode uses both a digital pressure switch and an
|
||||
* analog pressure sensor connected to analog channel 0 to cycle the compressor.
|
||||
*
|
||||
* <p>The compressor will turn on when <i>both</i>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The digital pressure switch indicates the system is not full AND
|
||||
* <li>The analog pressure sensor indicates that the pressure in the system is below the
|
||||
* specified minimum pressure.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The compressor will turn off when <i>either</i>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The digital pressure switch is disconnected or indicates that the system is full OR
|
||||
* <li>The pressure detected by the analog sensor is greater than the specified maximum
|
||||
* pressure.
|
||||
* </ul>
|
||||
*
|
||||
* @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure
|
||||
* drops below this value and the pressure switch indicates that the system is not full. Range
|
||||
* 0-120 PSI.
|
||||
* @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure
|
||||
* reaches this value or the pressure switch is disconnected or indicates that the system is
|
||||
* full. Range 0-120 PSI. Must be larger then minPressure.
|
||||
*/
|
||||
@Override
|
||||
public void enableCompressorHybrid(double minPressure, double maxPressure) {
|
||||
if (minPressure >= maxPressure) {
|
||||
throw new IllegalArgumentException("maxPressure must be greater than minPressure");
|
||||
}
|
||||
if (minPressure < 0 || minPressure > 120) {
|
||||
throw new IllegalArgumentException(
|
||||
"minPressure must be between 0 and 120 PSI, got " + minPressure);
|
||||
}
|
||||
if (maxPressure < 0 || maxPressure > 120) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxPressure must be between 0 and 120 PSI, got " + maxPressure);
|
||||
}
|
||||
|
||||
// Send the voltage as it would be if the 5V rail was at exactly 5V.
|
||||
// The firmware will compensate for the real 5V rail voltage, which
|
||||
// can fluctuate somewhat over time.
|
||||
double minAnalogVoltage = psiToVolts(minPressure, 5);
|
||||
double maxAnalogVoltage = psiToVolts(maxPressure, 5);
|
||||
REVPHJNI.setClosedLoopControlHybrid(m_handle, minAnalogVoltage, maxAnalogVoltage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw voltage of the specified analog input channel.
|
||||
*
|
||||
* @param channel The analog input channel to read voltage from.
|
||||
* @return The voltage of the specified analog input channel.
|
||||
*/
|
||||
@Override
|
||||
public double getAnalogVoltage(int channel) {
|
||||
return REVPHJNI.getAnalogVoltage(m_handle, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pressure read by an analog pressure sensor on the specified analog input channel.
|
||||
*
|
||||
* @param channel The analog input channel to read pressure from.
|
||||
* @return The pressure read by an analog pressure sensor on the specified analog input channel.
|
||||
*/
|
||||
@Override
|
||||
public double getPressure(int channel) {
|
||||
double sensorVoltage = REVPHJNI.getAnalogVoltage(m_handle, channel);
|
||||
double supplyVoltage = REVPHJNI.get5VVoltage(m_handle);
|
||||
return voltsToPsi(sensorVoltage, supplyVoltage);
|
||||
}
|
||||
|
||||
/** Clears the sticky faults. */
|
||||
public void clearStickyFaults() {
|
||||
REVPHJNI.clearStickyFaults(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hardware and firmware versions of this device.
|
||||
*
|
||||
* @return The hardware and firmware versions.
|
||||
*/
|
||||
public REVPHVersion getVersion() {
|
||||
return REVPHJNI.getVersion(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the faults currently active on this device.
|
||||
*
|
||||
* @return The faults.
|
||||
*/
|
||||
public REVPHFaults getFaults() {
|
||||
return REVPHJNI.getFaults(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sticky faults currently active on this device.
|
||||
*
|
||||
* @return The sticky faults.
|
||||
*/
|
||||
public REVPHStickyFaults getStickyFaults() {
|
||||
return REVPHJNI.getStickyFaults(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current input voltage for this device.
|
||||
*
|
||||
* @return The input voltage.
|
||||
*/
|
||||
public double getInputVoltage() {
|
||||
return REVPHJNI.getInputVoltage(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current voltage of the regulated 5v supply.
|
||||
*
|
||||
* @return The current voltage of the 5v supply.
|
||||
*/
|
||||
public double get5VRegulatedVoltage() {
|
||||
return REVPHJNI.get5VVoltage(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total current (in amps) drawn by all solenoids.
|
||||
*
|
||||
* @return Total current drawn by all solenoids in amps.
|
||||
*/
|
||||
public double getSolenoidsTotalCurrent() {
|
||||
return REVPHJNI.getSolenoidCurrent(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current voltage of the solenoid power supply.
|
||||
*
|
||||
* @return The current voltage of the solenoid power supply.
|
||||
*/
|
||||
public double getSolenoidsVoltage() {
|
||||
return REVPHJNI.getSolenoidVoltage(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportUsage(String device, String data) {
|
||||
HAL.reportUsage("PH[" + m_dataStore.m_module + "]/" + device, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// 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 pneumatics devices. */
|
||||
public interface PneumaticsBase extends AutoCloseable {
|
||||
/**
|
||||
* For internal use to get a module for a specific type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module module number
|
||||
* @param type module type
|
||||
* @return module
|
||||
*/
|
||||
static PneumaticsBase getForType(int busId, int module, PneumaticsModuleType type) {
|
||||
return switch (type) {
|
||||
case CTREPCM -> new PneumaticsControlModule(busId, module);
|
||||
case REVPH -> new PneumaticHub(busId, module);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use to get the default for a specific type.
|
||||
*
|
||||
* @param type module type
|
||||
* @return module default
|
||||
*/
|
||||
static int getDefaultForType(PneumaticsModuleType type) {
|
||||
return switch (type) {
|
||||
case CTREPCM -> SensorUtil.getDefaultCTREPCMModule();
|
||||
case REVPH -> SensorUtil.getDefaultREVPHModule();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets solenoids on a pneumatics module.
|
||||
*
|
||||
* @param mask Bitmask indicating which solenoids to set. The LSB represents solenoid 0.
|
||||
* @param values Bitmask indicating the desired states of the solenoids. The LSB represents
|
||||
* solenoid 0.
|
||||
*/
|
||||
void setSolenoids(int mask, int values);
|
||||
|
||||
/**
|
||||
* Gets a bitmask of solenoid values.
|
||||
*
|
||||
* @return Bitmask containing the state of the solenoids. The LSB represents solenoid 0.
|
||||
*/
|
||||
int getSolenoids();
|
||||
|
||||
/**
|
||||
* Get module number for this module.
|
||||
*
|
||||
* @return module number
|
||||
*/
|
||||
int getModuleNumber();
|
||||
|
||||
/**
|
||||
* Get a bitmask of disabled solenoids.
|
||||
*
|
||||
* @return Bitmask indicating disabled solenoids. The LSB represents solenoid 0.
|
||||
*/
|
||||
int getSolenoidDisabledList();
|
||||
|
||||
/**
|
||||
* Fire a single solenoid shot.
|
||||
*
|
||||
* @param index solenoid index
|
||||
*/
|
||||
void fireOneShot(int index);
|
||||
|
||||
/**
|
||||
* Set the duration for a single solenoid shot.
|
||||
*
|
||||
* @param index solenoid index
|
||||
* @param durMs shot duration
|
||||
*/
|
||||
void setOneShotDuration(int index, int durMs);
|
||||
|
||||
/**
|
||||
* Returns whether the compressor is active or not.
|
||||
*
|
||||
* @return True if the compressor is on - otherwise false.
|
||||
*/
|
||||
boolean getCompressor();
|
||||
|
||||
/**
|
||||
* Returns the state of the pressure switch.
|
||||
*
|
||||
* @return True if pressure switch indicates that the system is not full, otherwise false.
|
||||
*/
|
||||
boolean getPressureSwitch();
|
||||
|
||||
/**
|
||||
* Returns the current drawn by the compressor in amps.
|
||||
*
|
||||
* @return The current drawn by the compressor.
|
||||
*/
|
||||
double getCompressorCurrent();
|
||||
|
||||
/** Disables the compressor. */
|
||||
void disableCompressor();
|
||||
|
||||
/**
|
||||
* Enables the compressor in digital mode using the digital pressure switch. The compressor will
|
||||
* turn on when the pressure switch indicates that the system is not full, and will turn off when
|
||||
* the pressure switch indicates that the system is full.
|
||||
*/
|
||||
void enableCompressorDigital();
|
||||
|
||||
/**
|
||||
* If supported by the device, enables the compressor in analog mode. This mode uses an analog
|
||||
* pressure sensor connected to analog channel 0 to cycle the compressor. The compressor will turn
|
||||
* on when the pressure drops below {@code minPressure} and will turn off when the pressure
|
||||
* reaches {@code maxPressure}. This mode is only supported by the REV PH with the REV Analog
|
||||
* Pressure Sensor connected to analog channel 0.
|
||||
*
|
||||
* <p>On CTRE PCM, this will enable digital control.
|
||||
*
|
||||
* @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure
|
||||
* drops below this value.
|
||||
* @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure
|
||||
* reaches this value.
|
||||
*/
|
||||
void enableCompressorAnalog(double minPressure, double maxPressure);
|
||||
|
||||
/**
|
||||
* If supported by the device, enables the compressor in hybrid mode. This mode uses both a
|
||||
* digital pressure switch and an analog pressure sensor connected to analog channel 0 to cycle
|
||||
* the compressor. This mode is only supported by the REV PH with the REV Analog Pressure Sensor
|
||||
* connected to analog channel 0.
|
||||
*
|
||||
* <p>The compressor will turn on when <i>both</i>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The digital pressure switch indicates the system is not full AND
|
||||
* <li>The analog pressure sensor indicates that the pressure in the system is below the
|
||||
* specified minimum pressure.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The compressor will turn off when <i>either</i>:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The digital pressure switch is disconnected or indicates that the system is full OR
|
||||
* <li>The pressure detected by the analog sensor is greater than the specified maximum
|
||||
* pressure.
|
||||
* </ul>
|
||||
*
|
||||
* <p>On CTRE PCM, this will enable digital control.
|
||||
*
|
||||
* @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure
|
||||
* drops below this value and the pressure switch indicates that the system is not full.
|
||||
* @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure
|
||||
* reaches this value or the pressure switch is disconnected or indicates that the system is
|
||||
* full.
|
||||
*/
|
||||
void enableCompressorHybrid(double minPressure, double maxPressure);
|
||||
|
||||
/**
|
||||
* If supported by the device, returns the raw voltage of the specified analog input channel.
|
||||
*
|
||||
* <p>This function is only supported by the REV PH. On CTRE PCM, this will return 0.
|
||||
*
|
||||
* @param channel The analog input channel to read voltage from.
|
||||
* @return The voltage of the specified analog input channel.
|
||||
*/
|
||||
double getAnalogVoltage(int channel);
|
||||
|
||||
/**
|
||||
* If supported by the device, returns the pressure (in PSI) read by an analog pressure sensor on
|
||||
* the specified analog input channel.
|
||||
*
|
||||
* <p>This function is only supported by the REV PH. On CTRE PCM, this will return 0.
|
||||
*
|
||||
* @param channel The analog input channel to read pressure from.
|
||||
* @return The pressure (in PSI) read by an analog pressure sensor on the specified analog input
|
||||
* channel.
|
||||
*/
|
||||
double getPressure(int channel);
|
||||
|
||||
/**
|
||||
* Returns the active compressor configuration.
|
||||
*
|
||||
* @return The active compressor configuration.
|
||||
*/
|
||||
CompressorConfigType getCompressorConfigType();
|
||||
|
||||
/**
|
||||
* Check if a solenoid channel is valid.
|
||||
*
|
||||
* @param channel Channel to check
|
||||
* @return True if channel exists
|
||||
*/
|
||||
boolean checkSolenoidChannel(int channel);
|
||||
|
||||
/**
|
||||
* Check to see if the solenoids marked in the bitmask can be reserved, and if so, reserve them.
|
||||
*
|
||||
* @param mask The bitmask of solenoids to reserve. The LSB represents solenoid 0.
|
||||
* @return 0 if successful; mask of solenoids that couldn't be allocated otherwise
|
||||
*/
|
||||
int checkAndReserveSolenoids(int mask);
|
||||
|
||||
/**
|
||||
* Unreserve the solenoids marked in the bitmask.
|
||||
*
|
||||
* @param mask The bitmask of solenoids to unreserve. The LSB represents solenoid 0.
|
||||
*/
|
||||
void unreserveSolenoids(int mask);
|
||||
|
||||
/**
|
||||
* Reserve the compressor.
|
||||
*
|
||||
* @return true if successful; false if compressor already reserved
|
||||
*/
|
||||
boolean reserveCompressor();
|
||||
|
||||
/** Unreserve the compressor. */
|
||||
void unreserveCompressor();
|
||||
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Create a solenoid object for the specified channel.
|
||||
*
|
||||
* @param channel solenoid channel
|
||||
* @return Solenoid object
|
||||
*/
|
||||
Solenoid makeSolenoid(int channel);
|
||||
|
||||
/**
|
||||
* Create a double solenoid object for the specified channels.
|
||||
*
|
||||
* @param forwardChannel solenoid channel for forward
|
||||
* @param reverseChannel solenoid channel for reverse
|
||||
* @return DoubleSolenoid object
|
||||
*/
|
||||
DoubleSolenoid makeDoubleSolenoid(int forwardChannel, int reverseChannel);
|
||||
|
||||
/**
|
||||
* Create a compressor object.
|
||||
*
|
||||
* @return Compressor object
|
||||
*/
|
||||
Compressor makeCompressor();
|
||||
|
||||
/**
|
||||
* Report usage.
|
||||
*
|
||||
* @param device device and channel as appropriate
|
||||
* @param data arbitrary usage data
|
||||
*/
|
||||
void reportUsage(String device, String data);
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
// 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.hal.CTREPCMJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.PortsJNI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Module class for controlling a Cross The Road Electronics Pneumatics Control Module. */
|
||||
public class PneumaticsControlModule implements PneumaticsBase {
|
||||
private static class DataStore implements AutoCloseable {
|
||||
public final int m_module;
|
||||
public final int m_handle;
|
||||
private final int m_busId;
|
||||
private int m_refCount;
|
||||
private int m_reservedMask;
|
||||
private boolean m_compressorReserved;
|
||||
private final Object m_reserveLock = new Object();
|
||||
|
||||
DataStore(int busId, int module) {
|
||||
m_handle = CTREPCMJNI.initialize(busId, module);
|
||||
m_module = module;
|
||||
m_busId = busId;
|
||||
m_handleMaps[busId].put(module, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
CTREPCMJNI.free(m_handle);
|
||||
m_handleMaps[m_busId].remove(m_module);
|
||||
}
|
||||
|
||||
public void addRef() {
|
||||
m_refCount++;
|
||||
}
|
||||
|
||||
public void removeRef() {
|
||||
m_refCount--;
|
||||
if (m_refCount == 0) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static final Map<Integer, DataStore>[] m_handleMaps =
|
||||
(Map<Integer, DataStore>[]) new Map[PortsJNI.getNumCanBuses()];
|
||||
|
||||
private static final Object m_handleLock = new Object();
|
||||
|
||||
private static DataStore getForModule(int busId, int module) {
|
||||
synchronized (m_handleLock) {
|
||||
Map<Integer, DataStore> handleMap = m_handleMaps[busId];
|
||||
if (handleMap == null) {
|
||||
handleMap = new HashMap<>();
|
||||
m_handleMaps[busId] = handleMap;
|
||||
}
|
||||
|
||||
DataStore pcm = handleMap.get(module);
|
||||
if (pcm == null) {
|
||||
pcm = new DataStore(busId, module);
|
||||
}
|
||||
pcm.addRef();
|
||||
return pcm;
|
||||
}
|
||||
}
|
||||
|
||||
private static void freeModule(DataStore store) {
|
||||
synchronized (m_handleLock) {
|
||||
store.removeRef();
|
||||
}
|
||||
}
|
||||
|
||||
private final DataStore m_dataStore;
|
||||
private final int m_handle;
|
||||
|
||||
/**
|
||||
* Constructs a PneumaticsControlModule with the default ID (0).
|
||||
*
|
||||
* @param busId The bus ID
|
||||
*/
|
||||
public PneumaticsControlModule(int busId) {
|
||||
this(busId, SensorUtil.getDefaultCTREPCMModule());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a PneumaticsControlModule.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module module number to construct
|
||||
*/
|
||||
public PneumaticsControlModule(int busId, int module) {
|
||||
m_dataStore = getForModule(busId, module);
|
||||
m_handle = m_dataStore.m_handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
freeModule(m_dataStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getCompressor() {
|
||||
return CTREPCMJNI.getCompressor(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPressureSwitch() {
|
||||
return CTREPCMJNI.getPressureSwitch(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCompressorCurrent() {
|
||||
return CTREPCMJNI.getCompressorCurrent(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the compressor current is currently too high.
|
||||
*
|
||||
* @return True if the compressor current is too high, otherwise false.
|
||||
* @see #getCompressorCurrentTooHighStickyFault()
|
||||
*/
|
||||
public boolean getCompressorCurrentTooHighFault() {
|
||||
return CTREPCMJNI.getCompressorCurrentTooHighFault(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compressor current has been too high since sticky faults were last cleared.
|
||||
* This fault is persistent and can be cleared by {@link #clearAllStickyFaults()}
|
||||
*
|
||||
* @return True if the compressor current has been too high since sticky faults were last cleared.
|
||||
* @see #getCompressorCurrentTooHighFault()
|
||||
*/
|
||||
public boolean getCompressorCurrentTooHighStickyFault() {
|
||||
return CTREPCMJNI.getCompressorCurrentTooHighStickyFault(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compressor is currently shorted.
|
||||
*
|
||||
* @return True if the compressor is currently shorted, otherwise false.
|
||||
* @see #getCompressorShortedStickyFault()
|
||||
*/
|
||||
public boolean getCompressorShortedFault() {
|
||||
return CTREPCMJNI.getCompressorShortedFault(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compressor has been shorted since sticky faults were last cleared. This
|
||||
* fault is persistent and can be cleared by {@link #clearAllStickyFaults()}
|
||||
*
|
||||
* @return True if the compressor has been shorted since sticky faults were last cleared,
|
||||
* otherwise false.
|
||||
* @see #getCompressorShortedFault()
|
||||
*/
|
||||
public boolean getCompressorShortedStickyFault() {
|
||||
return CTREPCMJNI.getCompressorShortedStickyFault(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compressor is currently disconnected.
|
||||
*
|
||||
* @return True if compressor is currently disconnected, otherwise false.
|
||||
* @see #getCompressorNotConnectedStickyFault()
|
||||
*/
|
||||
public boolean getCompressorNotConnectedFault() {
|
||||
return CTREPCMJNI.getCompressorNotConnectedFault(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the compressor has been disconnected since sticky faults were last cleared.
|
||||
* This fault is persistent and can be cleared by {@link #clearAllStickyFaults()}
|
||||
*
|
||||
* @return True if the compressor has been disconnected since sticky faults were last cleared,
|
||||
* otherwise false.
|
||||
* @see #getCompressorNotConnectedFault()
|
||||
*/
|
||||
public boolean getCompressorNotConnectedStickyFault() {
|
||||
return CTREPCMJNI.getCompressorNotConnectedStickyFault(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSolenoids(int mask, int values) {
|
||||
CTREPCMJNI.setSolenoids(m_handle, mask, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSolenoids() {
|
||||
return CTREPCMJNI.getSolenoids(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModuleNumber() {
|
||||
return m_dataStore.m_module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSolenoidDisabledList() {
|
||||
return CTREPCMJNI.getSolenoidDisabledList(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the solenoid is currently reporting a voltage fault.
|
||||
*
|
||||
* @return True if solenoid is reporting a fault, otherwise false.
|
||||
* @see #getSolenoidVoltageStickyFault()
|
||||
*/
|
||||
public boolean getSolenoidVoltageFault() {
|
||||
return CTREPCMJNI.getSolenoidVoltageFault(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the solenoid has reported a voltage fault since sticky faults were last
|
||||
* cleared. This fault is persistent and can be cleared by ClearAllStickyFaults()
|
||||
*
|
||||
* @return True if solenoid is reporting a fault, otherwise false.
|
||||
* @see #getSolenoidVoltageFault()
|
||||
*/
|
||||
public boolean getSolenoidVoltageStickyFault() {
|
||||
return CTREPCMJNI.getSolenoidVoltageStickyFault(m_handle);
|
||||
}
|
||||
|
||||
/** Clears all sticky faults on this device. */
|
||||
public void clearAllStickyFaults() {
|
||||
CTREPCMJNI.clearAllStickyFaults(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireOneShot(int index) {
|
||||
CTREPCMJNI.fireOneShot(m_handle, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOneShotDuration(int index, int durMs) {
|
||||
CTREPCMJNI.setOneShotDuration(m_handle, index, durMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkSolenoidChannel(int channel) {
|
||||
return CTREPCMJNI.checkSolenoidChannel(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int checkAndReserveSolenoids(int mask) {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
if ((m_dataStore.m_reservedMask & mask) != 0) {
|
||||
return m_dataStore.m_reservedMask & mask;
|
||||
}
|
||||
m_dataStore.m_reservedMask |= mask;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unreserveSolenoids(int mask) {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
m_dataStore.m_reservedMask &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Solenoid makeSolenoid(int channel) {
|
||||
return new Solenoid(m_dataStore.m_module, PneumaticsModuleType.CTREPCM, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DoubleSolenoid makeDoubleSolenoid(int forwardChannel, int reverseChannel) {
|
||||
return new DoubleSolenoid(
|
||||
m_dataStore.m_module, PneumaticsModuleType.CTREPCM, forwardChannel, reverseChannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Compressor makeCompressor() {
|
||||
return new Compressor(m_dataStore.m_module, PneumaticsModuleType.CTREPCM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reserveCompressor() {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
if (m_dataStore.m_compressorReserved) {
|
||||
return false;
|
||||
}
|
||||
m_dataStore.m_compressorReserved = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unreserveCompressor() {
|
||||
synchronized (m_dataStore.m_reserveLock) {
|
||||
m_dataStore.m_compressorReserved = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the compressor. The compressor will not turn on until {@link
|
||||
* #enableCompressorDigital()} is called.
|
||||
*/
|
||||
@Override
|
||||
public void disableCompressor() {
|
||||
CTREPCMJNI.setClosedLoopControl(m_handle, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCompressorDigital() {
|
||||
CTREPCMJNI.setClosedLoopControl(m_handle, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compressor in digital mode. Analog mode is unsupported by the CTRE PCM.
|
||||
*
|
||||
* @param minPressure Unsupported.
|
||||
* @param maxPressure Unsupported.
|
||||
* @see #enableCompressorDigital()
|
||||
*/
|
||||
@Override
|
||||
public void enableCompressorAnalog(double minPressure, double maxPressure) {
|
||||
CTREPCMJNI.setClosedLoopControl(m_handle, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the compressor in digital mode. Hybrid mode is unsupported by the CTRE PCM.
|
||||
*
|
||||
* @param minPressure Unsupported.
|
||||
* @param maxPressure Unsupported.
|
||||
* @see #enableCompressorDigital()
|
||||
*/
|
||||
@Override
|
||||
public void enableCompressorHybrid(double minPressure, double maxPressure) {
|
||||
CTREPCMJNI.setClosedLoopControl(m_handle, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompressorConfigType getCompressorConfigType() {
|
||||
return CTREPCMJNI.getClosedLoopControl(m_handle)
|
||||
? CompressorConfigType.Digital
|
||||
: CompressorConfigType.Disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsupported by the CTRE PCM.
|
||||
*
|
||||
* @param channel Unsupported.
|
||||
* @return 0
|
||||
*/
|
||||
@Override
|
||||
public double getAnalogVoltage(int channel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsupported by the CTRE PCM.
|
||||
*
|
||||
* @param channel Unsupported.
|
||||
* @return 0
|
||||
*/
|
||||
@Override
|
||||
public double getPressure(int channel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportUsage(String device, String data) {
|
||||
HAL.reportUsage("PCM[" + m_dataStore.m_module + "]/" + device, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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;
|
||||
|
||||
/** Pneumatics module type. */
|
||||
public enum PneumaticsModuleType {
|
||||
/** CTRE PCM. */
|
||||
CTREPCM,
|
||||
/** REV PH. */
|
||||
REVPH
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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.hal.util.AllocationException;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Solenoid class for running high voltage Digital Output on a pneumatics module.
|
||||
*
|
||||
* <p>The Solenoid class is typically used for pneumatic solenoids, but could be used for any device
|
||||
* within the current spec of the module.
|
||||
*/
|
||||
public class Solenoid implements Sendable, AutoCloseable {
|
||||
private final int m_mask; // The channel mask
|
||||
private final int m_channel;
|
||||
private PneumaticsBase m_module;
|
||||
|
||||
/**
|
||||
* Constructs a solenoid for a default module and specified type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param moduleType The module type to use.
|
||||
* @param channel The channel the solenoid is on.
|
||||
*/
|
||||
public Solenoid(final int busId, final PneumaticsModuleType moduleType, final int channel) {
|
||||
this(busId, PneumaticsBase.getDefaultForType(moduleType), moduleType, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a solenoid for a specified module and type.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module The module ID to use.
|
||||
* @param moduleType The module type to use.
|
||||
* @param channel The channel the solenoid is on.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public Solenoid(
|
||||
final int busId, final int module, final PneumaticsModuleType moduleType, final int channel) {
|
||||
m_module = PneumaticsBase.getForType(busId, module, moduleType);
|
||||
|
||||
m_mask = 1 << channel;
|
||||
m_channel = channel;
|
||||
|
||||
if (!m_module.checkSolenoidChannel(channel)) {
|
||||
m_module.close();
|
||||
throw new IllegalArgumentException("Channel " + channel + " out of range");
|
||||
}
|
||||
|
||||
if (m_module.checkAndReserveSolenoids(m_mask) != 0) {
|
||||
m_module.close();
|
||||
throw new AllocationException("Solenoid already allocated");
|
||||
}
|
||||
|
||||
m_module.reportUsage("Solenoid[" + channel + "]", "Solenoid");
|
||||
SendableRegistry.add(this, "Solenoid", m_module.getModuleNumber(), channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
m_module.unreserveSolenoids(m_mask);
|
||||
m_module.close();
|
||||
m_module = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a solenoid.
|
||||
*
|
||||
* @param on True will turn the solenoid output on. False will turn the solenoid output off.
|
||||
*/
|
||||
public void set(boolean on) {
|
||||
int value = on ? (0xFFFF & m_mask) : 0;
|
||||
m_module.setSolenoids(m_mask, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the current value of the solenoid.
|
||||
*
|
||||
* @return True if the solenoid output is on or false if the solenoid output is off.
|
||||
*/
|
||||
public boolean get() {
|
||||
int currentAll = m_module.getSolenoids();
|
||||
return (currentAll & m_mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the value of the solenoid.
|
||||
*
|
||||
* <p>If the solenoid is set to on, it'll be turned off. If the solenoid is set to off, it'll be
|
||||
* turned on.
|
||||
*/
|
||||
public void toggle() {
|
||||
set(!get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channel this solenoid is connected to.
|
||||
*
|
||||
* @return The channel this solenoid is connected to.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if solenoid is DisabledListed. If a solenoid is shorted, it is added to the Disabled List
|
||||
* and disabled until power cycle, or until faults are cleared.
|
||||
*
|
||||
* @return If solenoid is disabled due to short.
|
||||
*/
|
||||
public boolean isDisabled() {
|
||||
return (m_module.getSolenoidDisabledList() & m_mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pulse duration in the pneumatics module. This is used in conjunction with the
|
||||
* startPulse method to allow the pneumatics module to control the timing of a pulse.
|
||||
*
|
||||
* <p>On the PCM, the timing can be controlled in 0.01 second increments, with a maximum of 2.55
|
||||
* seconds.
|
||||
*
|
||||
* <p>On the PH, the timing can be controlled in 0.001 second increments, with a maximum of 65.534
|
||||
* seconds.
|
||||
*
|
||||
* @param duration The duration of the pulse in seconds.
|
||||
* @see #startPulse()
|
||||
*/
|
||||
public void setPulseDuration(double duration) {
|
||||
long durationMS = (long) (duration * 1000);
|
||||
m_module.setOneShotDuration(m_channel, (int) durationMS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the pneumatics module to generate a pulse of the duration set in setPulseDuration.
|
||||
*
|
||||
* @see #setPulseDuration(double)
|
||||
*/
|
||||
public void startPulse() {
|
||||
m_module.fireOneShot(m_channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Solenoid");
|
||||
builder.setActuator(true);
|
||||
builder.addBooleanProperty("Value", this::get, this::set);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.PowerDistributionFaults;
|
||||
import edu.wpi.first.hal.PowerDistributionJNI;
|
||||
import edu.wpi.first.hal.PowerDistributionStickyFaults;
|
||||
import edu.wpi.first.hal.PowerDistributionVersion;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class for getting voltage, current, temperature, power and energy from the CTRE Power
|
||||
* Distribution Panel (PDP) or REV Power Distribution Hub (PDH) over CAN.
|
||||
*/
|
||||
public class PowerDistribution implements Sendable, AutoCloseable {
|
||||
private final int m_handle;
|
||||
private final int m_module;
|
||||
|
||||
/** Default module number. */
|
||||
public static final int kDefaultModule = PowerDistributionJNI.DEFAULT_MODULE;
|
||||
|
||||
/** Power distribution module type. */
|
||||
public enum ModuleType {
|
||||
/** CTRE (Cross The Road Electronics) Power Distribution Panel (PDP). */
|
||||
kCTRE(PowerDistributionJNI.CTRE_TYPE),
|
||||
/** REV Power Distribution Hub (PDH). */
|
||||
kRev(PowerDistributionJNI.REV_TYPE);
|
||||
|
||||
/** ModuleType value. */
|
||||
public final int value;
|
||||
|
||||
ModuleType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a PowerDistribution object.
|
||||
*
|
||||
* @param busId The bus ID
|
||||
* @param module The CAN ID of the PDP/PDH.
|
||||
* @param moduleType Module type (CTRE or REV).
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public PowerDistribution(int busId, int module, ModuleType moduleType) {
|
||||
m_handle = PowerDistributionJNI.initialize(busId, module, moduleType.value);
|
||||
m_module = PowerDistributionJNI.getModuleNumber(m_handle);
|
||||
|
||||
if (moduleType == ModuleType.kCTRE) {
|
||||
HAL.reportUsage("PDP", m_module, "");
|
||||
} else {
|
||||
HAL.reportUsage("PDH", m_module, "");
|
||||
}
|
||||
SendableRegistry.add(this, "PowerDistribution", m_module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a PowerDistribution object.
|
||||
*
|
||||
* <p>Detects the connected PDP/PDH using the default CAN ID (0 for CTRE and 1 for REV).
|
||||
*
|
||||
* @param busId The bus ID
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public PowerDistribution(int busId) {
|
||||
m_handle =
|
||||
PowerDistributionJNI.initialize(busId, kDefaultModule, PowerDistributionJNI.AUTOMATIC_TYPE);
|
||||
m_module = PowerDistributionJNI.getModuleNumber(m_handle);
|
||||
|
||||
if (PowerDistributionJNI.getType(m_handle) == PowerDistributionJNI.CTRE_TYPE) {
|
||||
HAL.reportUsage("PowerDistribution", m_module, "CTRE");
|
||||
} else {
|
||||
HAL.reportUsage("PowerDistribution", m_module, "Rev");
|
||||
}
|
||||
|
||||
SendableRegistry.add(this, "PowerDistribution", m_module);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of channels for this power distribution object.
|
||||
*
|
||||
* @return Number of output channels (16 for PDP, 24 for PDH).
|
||||
*/
|
||||
public int getNumChannels() {
|
||||
return PowerDistributionJNI.getNumChannels(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the input voltage of the PDP/PDH.
|
||||
*
|
||||
* @return The voltage in volts
|
||||
*/
|
||||
public double getVoltage() {
|
||||
return PowerDistributionJNI.getVoltage(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the temperature of the PDP.
|
||||
*
|
||||
* <p>Not supported on the Rev PDH and returns 0.
|
||||
*
|
||||
* @return The temperature in degrees Celsius
|
||||
*/
|
||||
public double getTemperature() {
|
||||
return PowerDistributionJNI.getTemperature(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the current of a single channel of the PDP/PDH.
|
||||
*
|
||||
* @param channel The channel (0-15 for PDP, 0-23 for PDH) to query
|
||||
* @return The current of the channel in Amperes
|
||||
*/
|
||||
public double getCurrent(int channel) {
|
||||
return PowerDistributionJNI.getChannelCurrent(m_handle, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all currents of the PDP.
|
||||
*
|
||||
* @return The current of each channel in Amperes
|
||||
*/
|
||||
public double[] getAllCurrents() {
|
||||
double[] currents = new double[getNumChannels()];
|
||||
PowerDistributionJNI.getAllCurrents(m_handle, currents);
|
||||
return currents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the current of all monitored channels.
|
||||
*
|
||||
* @return The current of all the channels in Amperes
|
||||
*/
|
||||
public double getTotalCurrent() {
|
||||
return PowerDistributionJNI.getTotalCurrent(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the total power drawn from the monitored channels of the PDP.
|
||||
*
|
||||
* <p>Not supported on the Rev PDH and returns 0.
|
||||
*
|
||||
* @return the total power in Watts
|
||||
*/
|
||||
public double getTotalPower() {
|
||||
return PowerDistributionJNI.getTotalPower(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the total energy drawn from the monitored channels of the PDP.
|
||||
*
|
||||
* <p>Not supported on the Rev PDH and returns 0.
|
||||
*
|
||||
* @return the total energy in Joules
|
||||
*/
|
||||
public double getTotalEnergy() {
|
||||
return PowerDistributionJNI.getTotalEnergy(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the total energy to 0 of the PDP.
|
||||
*
|
||||
* <p>Not supported on the Rev PDH and does nothing.
|
||||
*/
|
||||
public void resetTotalEnergy() {
|
||||
PowerDistributionJNI.resetTotalEnergy(m_handle);
|
||||
}
|
||||
|
||||
/** Clear all PDP/PDH sticky faults. */
|
||||
public void clearStickyFaults() {
|
||||
PowerDistributionJNI.clearStickyFaults(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets module number (CAN ID).
|
||||
*
|
||||
* @return The module number (CAN ID).
|
||||
*/
|
||||
public int getModule() {
|
||||
return m_module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module type for this power distribution object.
|
||||
*
|
||||
* @return The module type
|
||||
*/
|
||||
public ModuleType getType() {
|
||||
int type = PowerDistributionJNI.getType(m_handle);
|
||||
if (type == PowerDistributionJNI.REV_TYPE) {
|
||||
return ModuleType.kRev;
|
||||
} else {
|
||||
return ModuleType.kCTRE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the PDH switchable channel is turned on or off. Returns false with the CTRE PDP.
|
||||
*
|
||||
* @return The output state of the PDH switchable channel
|
||||
*/
|
||||
public boolean getSwitchableChannel() {
|
||||
return PowerDistributionJNI.getSwitchableChannel(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PDH switchable channel on or off. Does nothing with the CTRE PDP.
|
||||
*
|
||||
* @param enabled Whether to turn the PDH switchable channel on or off
|
||||
*/
|
||||
public void setSwitchableChannel(boolean enabled) {
|
||||
PowerDistributionJNI.setSwitchableChannel(m_handle, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the power distribution version number.
|
||||
*
|
||||
* @return The power distribution version number.
|
||||
*/
|
||||
public PowerDistributionVersion getVersion() {
|
||||
return PowerDistributionJNI.getVersion(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the power distribution faults.
|
||||
*
|
||||
* <p>On a CTRE PDP, this will return an object with no faults active.
|
||||
*
|
||||
* @return The power distribution faults.
|
||||
*/
|
||||
public PowerDistributionFaults getFaults() {
|
||||
return PowerDistributionJNI.getFaults(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the power distribution sticky faults.
|
||||
*
|
||||
* <p>On a CTRE PDP, this will return an object with no faults active.
|
||||
*
|
||||
* @return The power distribution sticky faults.
|
||||
*/
|
||||
public PowerDistributionStickyFaults getStickyFaults() {
|
||||
return PowerDistributionJNI.getStickyFaults(m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("PowerDistribution");
|
||||
int numChannels = getNumChannels();
|
||||
for (int i = 0; i < numChannels; ++i) {
|
||||
final int chan = i;
|
||||
builder.addDoubleProperty(
|
||||
"Chan" + i, () -> PowerDistributionJNI.getChannelCurrentNoError(m_handle, chan), null);
|
||||
}
|
||||
builder.addDoubleProperty(
|
||||
"Voltage", () -> PowerDistributionJNI.getVoltageNoError(m_handle), null);
|
||||
builder.addDoubleProperty(
|
||||
"TotalCurrent", () -> PowerDistributionJNI.getTotalCurrent(m_handle), null);
|
||||
builder.addBooleanProperty(
|
||||
"SwitchableChannel",
|
||||
() -> PowerDistributionJNI.getSwitchableChannelNoError(m_handle),
|
||||
value -> PowerDistributionJNI.setSwitchableChannel(m_handle, value));
|
||||
}
|
||||
}
|
||||
146
wpilibj/src/main/java/org/wpilib/hardware/range/SharpIR.java
Normal file
146
wpilibj/src/main/java/org/wpilib/hardware/range/SharpIR.java
Normal file
@@ -0,0 +1,146 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.hal.SimDevice.Direction;
|
||||
import edu.wpi.first.hal.SimDouble;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* SharpIR analog distance sensor class. These distance measuring sensors output an analog voltage
|
||||
* corresponding to the detection distance.
|
||||
*
|
||||
* <p>Teams are advised that the case of these sensors are conductive and grounded, so they should
|
||||
* not be mounted on a metallic surface on an FRC robot.
|
||||
*/
|
||||
@SuppressWarnings("MethodName")
|
||||
public class SharpIR implements Sendable, AutoCloseable {
|
||||
private AnalogInput m_sensor;
|
||||
|
||||
private SimDevice m_simDevice;
|
||||
private SimDouble m_simRange;
|
||||
|
||||
private final double m_A;
|
||||
private final double m_B;
|
||||
private final double m_min; // m
|
||||
private final double m_max; // m
|
||||
|
||||
/**
|
||||
* Sharp GP2Y0A02YK0F is an analog IR sensor capable of measuring distances from 20 cm to 150 cm.
|
||||
*
|
||||
* @param channel Analog input channel the sensor is connected to
|
||||
* @return sensor object
|
||||
*/
|
||||
public static SharpIR GP2Y0A02YK0F(int channel) {
|
||||
return new SharpIR(channel, 62.28, -1.092, 0.2, 1.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sharp GP2Y0A21YK0F is an analog IR sensor capable of measuring distances from 10 cm to 80 cm.
|
||||
*
|
||||
* @param channel Analog input channel the sensor is connected to
|
||||
* @return sensor object
|
||||
*/
|
||||
public static SharpIR GP2Y0A21YK0F(int channel) {
|
||||
return new SharpIR(channel, 26.449, -1.226, 0.1, 0.8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sharp GP2Y0A41SK0F is an analog IR sensor capable of measuring distances from 4 cm to 30 cm.
|
||||
*
|
||||
* @param channel Analog input channel the sensor is connected to
|
||||
* @return sensor object
|
||||
*/
|
||||
public static SharpIR GP2Y0A41SK0F(int channel) {
|
||||
return new SharpIR(channel, 12.354, -1.07, 0.04, 0.3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sharp GP2Y0A51SK0F is an analog IR sensor capable of measuring distances from 2 cm to 15 cm.
|
||||
*
|
||||
* @param channel Analog input channel the sensor is connected to
|
||||
* @return sensor object
|
||||
*/
|
||||
public static SharpIR GP2Y0A51SK0F(int channel) {
|
||||
return new SharpIR(channel, 5.2819, -1.161, 0.02, 0.15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually construct a SharpIR object. The distance is computed using this formula: A*v ^ B.
|
||||
* Prefer to use one of the static factories to create this device instead.
|
||||
*
|
||||
* @param channel AnalogInput channel
|
||||
* @param a Constant A
|
||||
* @param b Constant B
|
||||
* @param min Minimum distance to report in meters
|
||||
* @param max Maximum distance to report in meters
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public SharpIR(int channel, double a, double b, double min, double max) {
|
||||
m_sensor = new AnalogInput(channel);
|
||||
|
||||
m_A = a;
|
||||
m_B = b;
|
||||
m_min = min;
|
||||
m_max = max;
|
||||
|
||||
HAL.reportUsage("IO", channel, "SharpIR");
|
||||
SendableRegistry.add(this, "SharpIR", channel);
|
||||
|
||||
m_simDevice = SimDevice.create("SharpIR", m_sensor.getChannel());
|
||||
if (m_simDevice != null) {
|
||||
m_simRange = m_simDevice.createDouble("Range (m)", Direction.kInput, 0.0);
|
||||
m_sensor.setSimDevice(m_simDevice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
m_sensor.close();
|
||||
m_sensor = null;
|
||||
|
||||
if (m_simDevice != null) {
|
||||
m_simDevice.close();
|
||||
m_simDevice = null;
|
||||
m_simRange = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the analog input channel number.
|
||||
*
|
||||
* @return analog input channel
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_sensor.getChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range in meters from the distance sensor.
|
||||
*
|
||||
* @return range in meters of the target returned by the sensor
|
||||
*/
|
||||
public double getRange() {
|
||||
if (m_simRange != null) {
|
||||
return Math.clamp(m_simRange.get(), m_min, m_max);
|
||||
} else {
|
||||
// Don't allow zero/negative values
|
||||
var v = Math.max(m_sensor.getVoltage(), 0.00001);
|
||||
|
||||
return Math.clamp(m_A * Math.pow(v, m_B) * 1e-2, m_min, m_max);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Ultrasonic");
|
||||
builder.addDoubleProperty("Value", this::getRange, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.hal.SimDevice.Direction;
|
||||
import edu.wpi.first.hal.SimDouble;
|
||||
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;
|
||||
|
||||
/** Class for supporting continuous analog encoders, such as the US Digital MA3. */
|
||||
public class AnalogEncoder implements Sendable, AutoCloseable {
|
||||
private final AnalogInput m_analogInput;
|
||||
private boolean m_ownsAnalogInput;
|
||||
private double m_fullRange;
|
||||
private double m_expectedZero;
|
||||
private double m_sensorMin;
|
||||
private double m_sensorMax = 1.0;
|
||||
private boolean m_isInverted;
|
||||
|
||||
private SimDevice m_simDevice;
|
||||
private SimDouble m_simPosition;
|
||||
|
||||
/**
|
||||
* Construct a new AnalogEncoder attached to a specific AnalogIn channel.
|
||||
*
|
||||
* @param channel the analog input channel to attach to
|
||||
* @param fullRange the value to report at maximum travel
|
||||
* @param expectedZero the reading where you would expect a 0 from get()
|
||||
*/
|
||||
public AnalogEncoder(int channel, double fullRange, double expectedZero) {
|
||||
this(new AnalogInput(channel), fullRange, expectedZero);
|
||||
m_ownsAnalogInput = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new AnalogEncoder attached to a specific AnalogInput.
|
||||
*
|
||||
* @param analogInput the analog input to attach to
|
||||
* @param fullRange the value to report at maximum travel
|
||||
* @param expectedZero the reading where you would expect a 0 from get()
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogEncoder(AnalogInput analogInput, double fullRange, double expectedZero) {
|
||||
m_analogInput = analogInput;
|
||||
init(fullRange, expectedZero);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new AnalogEncoder attached to a specific AnalogIn channel.
|
||||
*
|
||||
* <p>This has a fullRange of 1 and an expectedZero of 0.
|
||||
*
|
||||
* @param channel the analog input channel to attach to
|
||||
*/
|
||||
public AnalogEncoder(int channel) {
|
||||
this(channel, 1.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new AnalogEncoder attached to a specific AnalogInput.
|
||||
*
|
||||
* <p>This has a fullRange of 1 and an expectedZero of 0.
|
||||
*
|
||||
* @param analogInput the analog input to attach to
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogEncoder(AnalogInput analogInput) {
|
||||
this(analogInput, 1.0, 0.0);
|
||||
}
|
||||
|
||||
private void init(double fullRange, double expectedZero) {
|
||||
m_simDevice = SimDevice.create("AnalogEncoder", m_analogInput.getChannel());
|
||||
|
||||
if (m_simDevice != null) {
|
||||
m_simPosition = m_simDevice.createDouble("Position", Direction.kInput, 0.0);
|
||||
}
|
||||
|
||||
m_fullRange = fullRange;
|
||||
m_expectedZero = expectedZero;
|
||||
|
||||
HAL.reportUsage("IO", m_analogInput.getChannel(), "AnalogEncoder");
|
||||
|
||||
SendableRegistry.add(this, "Analog Encoder", m_analogInput.getChannel());
|
||||
}
|
||||
|
||||
private double mapSensorRange(double pos) {
|
||||
// map sensor range
|
||||
if (pos < m_sensorMin) {
|
||||
pos = m_sensorMin;
|
||||
}
|
||||
if (pos > m_sensorMax) {
|
||||
pos = m_sensorMax;
|
||||
}
|
||||
pos = (pos - m_sensorMin) / (m_sensorMax - m_sensorMin);
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encoder value.
|
||||
*
|
||||
* @return the encoder value scaled by the full range input
|
||||
*/
|
||||
public double get() {
|
||||
if (m_simPosition != null) {
|
||||
return m_simPosition.get();
|
||||
}
|
||||
|
||||
double analog = m_analogInput.getVoltage();
|
||||
double pos = analog / RobotController.getVoltage3V3();
|
||||
|
||||
// Map sensor range if range isn't full
|
||||
pos = mapSensorRange(pos);
|
||||
|
||||
// Compute full range and offset
|
||||
pos = pos * m_fullRange - m_expectedZero;
|
||||
|
||||
// Map from 0 - Full Range
|
||||
double result = MathUtil.inputModulus(pos, 0, m_fullRange);
|
||||
// Invert if necessary
|
||||
if (m_isInverted) {
|
||||
return m_fullRange - result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encoder voltage percentage range. Analog sensors are not always fully stable at the end
|
||||
* of their travel ranges. Shrinking this range down can help mitigate issues with that.
|
||||
*
|
||||
* @param min minimum voltage percentage (0-1 range)
|
||||
* @param max maximum voltage percentage (0-1 range)
|
||||
*/
|
||||
public void setVoltagePercentageRange(double min, double max) {
|
||||
m_sensorMin = Math.clamp(min, 0.0, 1.0);
|
||||
m_sensorMax = Math.clamp(max, 0.0, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if this encoder is inverted.
|
||||
*
|
||||
* @param inverted true to invert the encoder, false otherwise
|
||||
*/
|
||||
public void setInverted(boolean inverted) {
|
||||
m_isInverted = inverted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channel number.
|
||||
*
|
||||
* @return The channel number.
|
||||
*/
|
||||
public int getChannel() {
|
||||
return m_analogInput.getChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_ownsAnalogInput) {
|
||||
m_analogInput.close();
|
||||
}
|
||||
if (m_simDevice != null) {
|
||||
m_simDevice.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("AbsoluteEncoder");
|
||||
builder.addDoubleProperty("Position", this::get, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Class for reading analog potentiometers. Analog potentiometers read in an analog voltage that
|
||||
* corresponds to a position. The position is in whichever units you choose, by way of the scaling
|
||||
* and offset constants passed to the constructor.
|
||||
*/
|
||||
public class AnalogPotentiometer implements Sendable, AutoCloseable {
|
||||
private AnalogInput m_analogInput;
|
||||
private boolean m_initAnalogInput;
|
||||
private double m_fullRange;
|
||||
private double m_offset;
|
||||
|
||||
/**
|
||||
* AnalogPotentiometer constructor.
|
||||
*
|
||||
* <p>Use the fullRange and offset values so that the output produces meaningful values. I.E: you
|
||||
* have a 270 degree potentiometer, and you want the output to be degrees with the halfway point
|
||||
* as 0 degrees. The fullRange value is 270.0(degrees) and the offset is -135.0 since the halfway
|
||||
* point after scaling is 135 degrees. This will calculate the result from the fullRange times the
|
||||
* fraction of the supply voltage, plus the offset.
|
||||
*
|
||||
* @param channel The analog input channel this potentiometer is plugged into. 0-3 are on-board
|
||||
* and 4-7 are on the MXP port.
|
||||
* @param fullRange The scaling to multiply the fraction by to get a meaningful unit.
|
||||
* @param offset The offset to add to the scaled value for controlling the zero value
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogPotentiometer(final int channel, double fullRange, double offset) {
|
||||
this(new AnalogInput(channel), fullRange, offset);
|
||||
m_initAnalogInput = true;
|
||||
SendableRegistry.addChild(this, m_analogInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* AnalogPotentiometer constructor.
|
||||
*
|
||||
* <p>Use the fullRange and offset values so that the output produces meaningful values. I.E: you
|
||||
* have a 270 degree potentiometer, and you want the output to be degrees with the halfway point
|
||||
* as 0 degrees. The fullRange value is 270.0(degrees) and the offset is -135.0 since the halfway
|
||||
* point after scaling is 135 degrees. This will calculate the result from the fullRange times the
|
||||
* fraction of the supply voltage, plus the offset.
|
||||
*
|
||||
* @param input The {@link AnalogInput} this potentiometer is plugged into.
|
||||
* @param fullRange The angular value (in desired units) representing the full 0-3.3V range of the
|
||||
* input.
|
||||
* @param offset The angular value (in desired units) representing the angular output at 0V.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public AnalogPotentiometer(final AnalogInput input, double fullRange, double offset) {
|
||||
SendableRegistry.add(this, "AnalogPotentiometer", input.getChannel());
|
||||
m_analogInput = input;
|
||||
m_initAnalogInput = false;
|
||||
|
||||
m_fullRange = fullRange;
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* AnalogPotentiometer constructor.
|
||||
*
|
||||
* <p>Use the scale value so that the output produces meaningful values. I.E: you have a 270
|
||||
* degree potentiometer, and you want the output to be degrees with the starting point as 0
|
||||
* degrees. The scale value is 270.0(degrees).
|
||||
*
|
||||
* @param channel The analog input channel this potentiometer is plugged into. 0-3 are on-board
|
||||
* and 4-7 are on the MXP port.
|
||||
* @param scale The scaling to multiply the voltage by to get a meaningful unit.
|
||||
*/
|
||||
public AnalogPotentiometer(final int channel, double scale) {
|
||||
this(channel, scale, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* AnalogPotentiometer constructor.
|
||||
*
|
||||
* <p>Use the fullRange and offset values so that the output produces meaningful values. I.E: you
|
||||
* have a 270 degree potentiometer, and you want the output to be degrees with the starting point
|
||||
* as 0 degrees. The scale value is 270.0(degrees).
|
||||
*
|
||||
* @param input The {@link AnalogInput} this potentiometer is plugged into.
|
||||
* @param scale The scaling to multiply the voltage by to get a meaningful unit.
|
||||
*/
|
||||
public AnalogPotentiometer(final AnalogInput input, double scale) {
|
||||
this(input, scale, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* AnalogPotentiometer constructor.
|
||||
*
|
||||
* <p>The potentiometer will return a value between 0 and 1.0.
|
||||
*
|
||||
* @param channel The analog input channel this potentiometer is plugged into. 0-3 are on-board
|
||||
* and 4-7 are on the MXP port.
|
||||
*/
|
||||
public AnalogPotentiometer(final int channel) {
|
||||
this(channel, 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* AnalogPotentiometer constructor.
|
||||
*
|
||||
* <p>The potentiometer will return a value between 0 and 1.0.
|
||||
*
|
||||
* @param input The {@link AnalogInput} this potentiometer is plugged into.
|
||||
*/
|
||||
public AnalogPotentiometer(final AnalogInput input) {
|
||||
this(input, 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current reading of the potentiometer.
|
||||
*
|
||||
* @return The current position of the potentiometer.
|
||||
*/
|
||||
public double get() {
|
||||
if (m_analogInput == null) {
|
||||
return m_offset;
|
||||
}
|
||||
return (m_analogInput.getVoltage() / RobotController.getVoltage3V3()) * m_fullRange + m_offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
if (m_analogInput != null) {
|
||||
builder.setSmartDashboardType("Analog Input");
|
||||
builder.addDoubleProperty("Value", this::get, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
if (m_initAnalogInput) {
|
||||
m_analogInput.close();
|
||||
m_analogInput = null;
|
||||
m_initAnalogInput = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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.hal.DutyCycleJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class to read a duty cycle PWM input.
|
||||
*
|
||||
* <p>PWM input signals are specified with a frequency and a ratio of high to low in that frequency.
|
||||
* There are 8 of these in the roboRIO, and they can be attached to any SmartIO Channel.
|
||||
*
|
||||
* <p>These can be combined as the input of an AnalogTrigger to a Counter in order to implement
|
||||
* rollover checking.
|
||||
*/
|
||||
public class DutyCycle implements Sendable, AutoCloseable {
|
||||
// Explicitly package private
|
||||
final int m_handle;
|
||||
private final int m_channel;
|
||||
|
||||
/**
|
||||
* Constructs a DutyCycle input from a smartio channel.
|
||||
*
|
||||
* @param channel The channel to use.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DutyCycle(int channel) {
|
||||
m_handle = DutyCycleJNI.initialize(channel);
|
||||
|
||||
m_channel = channel;
|
||||
HAL.reportUsage("IO", channel, "DutyCycle");
|
||||
SendableRegistry.add(this, "Duty Cycle", channel);
|
||||
}
|
||||
|
||||
/** Close the DutyCycle and free all resources. */
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
DutyCycleJNI.free(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frequency of the duty cycle signal.
|
||||
*
|
||||
* @return frequency in Hertz
|
||||
*/
|
||||
public double getFrequency() {
|
||||
return DutyCycleJNI.getFrequency(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the output ratio of the duty cycle signal.
|
||||
*
|
||||
* <p>0 means always low, 1 means always high.
|
||||
*
|
||||
* @return output ratio between 0 and 1
|
||||
*/
|
||||
public double getOutput() {
|
||||
return DutyCycleJNI.getOutput(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw high time of the duty cycle signal.
|
||||
*
|
||||
* @return high time of last pulse in nanoseconds
|
||||
*/
|
||||
public int getHighTimeNanoseconds() {
|
||||
return DutyCycleJNI.getHighTime(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channel of the source.
|
||||
*
|
||||
* @return the source channel
|
||||
*/
|
||||
public int getSourceChannel() {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("Duty Cycle");
|
||||
builder.addDoubleProperty("Frequency", this::getFrequency, null);
|
||||
builder.addDoubleProperty("Output", this::getOutput, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// 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.hal.SimBoolean;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.hal.SimDouble;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class for supporting duty cycle/PWM encoders, such as the US Digital MA3 with PWM Output, the
|
||||
* CTRE Mag Encoder, the Rev Hex Encoder, and the AM Mag Encoder.
|
||||
*/
|
||||
public class DutyCycleEncoder implements Sendable, AutoCloseable {
|
||||
private final DutyCycle m_dutyCycle;
|
||||
private boolean m_ownsDutyCycle;
|
||||
private double m_frequencyThreshold = 100;
|
||||
private double m_fullRange;
|
||||
private double m_expectedZero;
|
||||
private double m_periodNanos;
|
||||
private double m_sensorMin;
|
||||
private double m_sensorMax = 1.0;
|
||||
private boolean m_isInverted;
|
||||
|
||||
private SimDevice m_simDevice;
|
||||
private SimDouble m_simPosition;
|
||||
private SimBoolean m_simIsConnected;
|
||||
|
||||
/**
|
||||
* Construct a new DutyCycleEncoder on a specific channel.
|
||||
*
|
||||
* @param channel the channel to attach to
|
||||
* @param fullRange the value to report at maximum travel
|
||||
* @param expectedZero the reading where you would expect a 0 from get()
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DutyCycleEncoder(int channel, double fullRange, double expectedZero) {
|
||||
m_ownsDutyCycle = true;
|
||||
m_dutyCycle = new DutyCycle(channel);
|
||||
init(fullRange, expectedZero);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new DutyCycleEncoder attached to an existing DutyCycle object.
|
||||
*
|
||||
* @param dutyCycle the duty cycle to attach to
|
||||
* @param fullRange the value to report at maximum travel
|
||||
* @param expectedZero the reading where you would expect a 0 from get()
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DutyCycleEncoder(DutyCycle dutyCycle, double fullRange, double expectedZero) {
|
||||
m_dutyCycle = dutyCycle;
|
||||
init(fullRange, expectedZero);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new DutyCycleEncoder on a specific channel.
|
||||
*
|
||||
* <p>This has a fullRange of 1 and an expectedZero of 0.
|
||||
*
|
||||
* @param channel the channel to attach to
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DutyCycleEncoder(int channel) {
|
||||
this(channel, 1.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new DutyCycleEncoder attached to an existing DutyCycle object.
|
||||
*
|
||||
* <p>This has a fullRange of 1 and an expectedZero of 0.
|
||||
*
|
||||
* @param dutyCycle the duty cycle to attach to
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public DutyCycleEncoder(DutyCycle dutyCycle) {
|
||||
this(dutyCycle, 1.0, 0.0);
|
||||
}
|
||||
|
||||
private void init(double fullRange, double expectedZero) {
|
||||
m_simDevice = SimDevice.create("DutyCycle:DutyCycleEncoder", m_dutyCycle.getSourceChannel());
|
||||
|
||||
if (m_simDevice != null) {
|
||||
m_simPosition = m_simDevice.createDouble("Position", SimDevice.Direction.kInput, 0.0);
|
||||
m_simIsConnected = m_simDevice.createBoolean("Connected", SimDevice.Direction.kInput, true);
|
||||
}
|
||||
|
||||
m_fullRange = fullRange;
|
||||
m_expectedZero = expectedZero;
|
||||
|
||||
SendableRegistry.add(this, "DutyCycle Encoder", m_dutyCycle.getSourceChannel());
|
||||
}
|
||||
|
||||
private double mapSensorRange(double pos) {
|
||||
// map sensor range
|
||||
if (pos < m_sensorMin) {
|
||||
pos = m_sensorMin;
|
||||
}
|
||||
if (pos > m_sensorMax) {
|
||||
pos = m_sensorMax;
|
||||
}
|
||||
pos = (pos - m_sensorMin) / (m_sensorMax - m_sensorMin);
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encoder value since the last reset.
|
||||
*
|
||||
* <p>This is reported in rotations since the last reset.
|
||||
*
|
||||
* @return the encoder value in rotations
|
||||
*/
|
||||
public double get() {
|
||||
if (m_simPosition != null) {
|
||||
return m_simPosition.get();
|
||||
}
|
||||
|
||||
double pos;
|
||||
// Compute output percentage (0-1)
|
||||
if (m_periodNanos == 0.0) {
|
||||
pos = m_dutyCycle.getOutput();
|
||||
} else {
|
||||
int highTime = m_dutyCycle.getHighTimeNanoseconds();
|
||||
pos = highTime / m_periodNanos;
|
||||
}
|
||||
|
||||
// Map sensor range if range isn't full
|
||||
pos = mapSensorRange(pos);
|
||||
|
||||
// Compute full range and offset
|
||||
pos = pos * m_fullRange - m_expectedZero;
|
||||
|
||||
// Map from 0 - Full Range
|
||||
double result = MathUtil.inputModulus(pos, 0, m_fullRange);
|
||||
// Invert if necessary
|
||||
if (m_isInverted) {
|
||||
return m_fullRange - result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encoder duty cycle range. As the encoder needs to maintain a duty cycle, the duty cycle
|
||||
* cannot go all the way to 0% or all the way to 100%. For example, an encoder with a 4096 us
|
||||
* period might have a minimum duty cycle of 1 us / 4096 us and a maximum duty cycle of 4095 /
|
||||
* 4096 us. Setting the range will result in an encoder duty cycle less than or equal to the
|
||||
* minimum being output as 0 rotation, the duty cycle greater than or equal to the maximum being
|
||||
* output as 1 rotation, and values in between linearly scaled from 0 to 1.
|
||||
*
|
||||
* @param min minimum duty cycle (0-1 range)
|
||||
* @param max maximum duty cycle (0-1 range)
|
||||
*/
|
||||
public void setDutyCycleRange(double min, double max) {
|
||||
m_sensorMin = Math.clamp(min, 0.0, 1.0);
|
||||
m_sensorMax = Math.clamp(max, 0.0, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frequency in Hz of the duty cycle signal from the encoder.
|
||||
*
|
||||
* @return duty cycle frequency in Hz
|
||||
*/
|
||||
public double getFrequency() {
|
||||
return m_dutyCycle.getFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the sensor is connected
|
||||
*
|
||||
* <p>This uses the duty cycle frequency to determine if the sensor is connected. By default, a
|
||||
* value of 100 Hz is used as the threshold, and this value can be changed with {@link
|
||||
* #setConnectedFrequencyThreshold(int)}.
|
||||
*
|
||||
* @return true if the sensor is connected
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
if (m_simIsConnected != null) {
|
||||
return m_simIsConnected.get();
|
||||
}
|
||||
return getFrequency() > m_frequencyThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the frequency threshold for detecting connection used by {@link #isConnected()}.
|
||||
*
|
||||
* @param frequency the minimum frequency in Hz.
|
||||
*/
|
||||
public void setConnectedFrequencyThreshold(double frequency) {
|
||||
if (frequency < 0) {
|
||||
frequency = 0;
|
||||
}
|
||||
|
||||
m_frequencyThreshold = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the assumed frequency of the connected device.
|
||||
*
|
||||
* <p>By default, the DutyCycle engine has to compute the frequency of the input signal. This can
|
||||
* result in both delayed readings and jumpy readings. To solve this, you can pass the expected
|
||||
* frequency of the sensor to this function. This will use that frequency to compute the DutyCycle
|
||||
* percentage, rather than the computed frequency.
|
||||
*
|
||||
* @param frequency the assumed frequency of the sensor
|
||||
*/
|
||||
public void setAssumedFrequency(double frequency) {
|
||||
if (frequency == 0.0) {
|
||||
m_periodNanos = 0.0;
|
||||
} else {
|
||||
m_periodNanos = 1000000000 / frequency;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if this encoder is inverted.
|
||||
*
|
||||
* @param inverted true to invert the encoder, false otherwise
|
||||
*/
|
||||
public void setInverted(boolean inverted) {
|
||||
m_isInverted = inverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_ownsDutyCycle) {
|
||||
m_dutyCycle.close();
|
||||
}
|
||||
if (m_simDevice != null) {
|
||||
m_simDevice.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channel of the source.
|
||||
*
|
||||
* @return the source channel
|
||||
*/
|
||||
public int getSourceChannel() {
|
||||
return m_dutyCycle.getSourceChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
builder.setSmartDashboardType("AbsoluteEncoder");
|
||||
builder.addDoubleProperty("Position", this::get, null);
|
||||
builder.addBooleanProperty("Is Connected", this::isConnected, null);
|
||||
}
|
||||
}
|
||||
357
wpilibj/src/main/java/org/wpilib/hardware/rotation/Encoder.java
Normal file
357
wpilibj/src/main/java/org/wpilib/hardware/rotation/Encoder.java
Normal file
@@ -0,0 +1,357 @@
|
||||
// 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 static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import edu.wpi.first.hal.EncoderJNI;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.SimDevice;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
|
||||
/**
|
||||
* Class to read quadrature encoders.
|
||||
*
|
||||
* <p>Quadrature encoders are devices that count shaft rotation and can sense direction. The output
|
||||
* of the Encoder class is an integer that can count either up or down, and can go negative for
|
||||
* reverse direction counting. When creating Encoders, a direction can be supplied that inverts the
|
||||
* sense of the output to make code more readable if the encoder is mounted such that forward
|
||||
* movement generates negative values. Quadrature encoders have two digital outputs, an A Channel
|
||||
* and a B Channel, that are out of phase with each other for direction sensing.
|
||||
*
|
||||
* <p>All encoders will immediately start counting - reset() them if you need them to be zeroed
|
||||
* before use.
|
||||
*/
|
||||
public class Encoder implements CounterBase, Sendable, AutoCloseable {
|
||||
private final EncodingType m_encodingType;
|
||||
|
||||
int m_encoder; // the HAL encoder object
|
||||
|
||||
/**
|
||||
* Common initialization code for Encoders. This code allocates resources for Encoders and is
|
||||
* common to all constructors.
|
||||
*
|
||||
* <p>The encoder will start counting immediately.
|
||||
*
|
||||
* @param aChannel The a channel.
|
||||
* @param bChannel The b channel.
|
||||
* @param reverseDirection If true, counts down instead of up (this is all relative)
|
||||
*/
|
||||
private void initEncoder(
|
||||
int aChannel, int bChannel, boolean reverseDirection, final EncodingType type) {
|
||||
m_encoder = EncoderJNI.initializeEncoder(aChannel, bChannel, reverseDirection, type.value);
|
||||
|
||||
String typeStr =
|
||||
switch (type) {
|
||||
case k1X -> "Encoder:1x";
|
||||
case k2X -> "Encoder:2x";
|
||||
case k4X -> "Encoder:4x";
|
||||
default -> "Encoder";
|
||||
};
|
||||
HAL.reportUsage("IO[" + aChannel + "," + bChannel + "]", typeStr);
|
||||
|
||||
int fpgaIndex = getFPGAIndex();
|
||||
SendableRegistry.add(this, "Encoder", fpgaIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoder constructor. Construct a Encoder given a and b channels.
|
||||
*
|
||||
* <p>The encoder will start counting immediately.
|
||||
*
|
||||
* @param channelA The 'a' channel DIO channel. 0-9 are on-board, 10-25 are on the MXP port
|
||||
* @param channelB The 'b' channel DIO channel. 0-9 are on-board, 10-25 are on the MXP port
|
||||
* @param reverseDirection represents the orientation of the encoder and inverts the output values
|
||||
* if necessary so forward represents positive values.
|
||||
*/
|
||||
public Encoder(final int channelA, final int channelB, boolean reverseDirection) {
|
||||
this(channelA, channelB, reverseDirection, EncodingType.k4X);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoder constructor. Construct an Encoder given a and b channels.
|
||||
*
|
||||
* <p>The encoder will start counting immediately.
|
||||
*
|
||||
* @param channelA The a channel digital input channel.
|
||||
* @param channelB The b channel digital input channel.
|
||||
*/
|
||||
public Encoder(final int channelA, final int channelB) {
|
||||
this(channelA, channelB, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoder constructor. Construct an Encoder given a and b channels.
|
||||
*
|
||||
* <p>The encoder will start counting immediately.
|
||||
*
|
||||
* @param channelA The a channel digital input channel.
|
||||
* @param channelB The b channel digital input channel.
|
||||
* @param reverseDirection represents the orientation of the encoder and inverts the output values
|
||||
* if necessary so forward represents positive values.
|
||||
* @param encodingType either k1X, k2X, or k4X to indicate 1X, 2X or 4X decoding. If 4X is
|
||||
* selected, then an encoder FPGA object is used and the returned counts will be 4x the
|
||||
* encoder spec'd value since all rising and falling edges are counted. If 1X or 2X are
|
||||
* selected, then a counter object will be used and the returned value will either exactly
|
||||
* match the spec'd count or be double (2x) the spec'd count.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public Encoder(
|
||||
final int channelA,
|
||||
final int channelB,
|
||||
boolean reverseDirection,
|
||||
final EncodingType encodingType) {
|
||||
requireNonNullParam(encodingType, "encodingType", "Encoder");
|
||||
|
||||
m_encodingType = encodingType;
|
||||
// SendableRegistry.addChild(this, m_aSource);
|
||||
// SendableRegistry.addChild(this, m_bSource);
|
||||
initEncoder(channelA, channelB, reverseDirection, encodingType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FPGA index of the encoder.
|
||||
*
|
||||
* @return The Encoder's FPGA index.
|
||||
*/
|
||||
public int getFPGAIndex() {
|
||||
return EncoderJNI.getEncoderFPGAIndex(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to divide raw edge counts down to spec'd counts.
|
||||
*
|
||||
* @return The encoding scale factor 1x, 2x, or 4x, per the requested encoding type.
|
||||
*/
|
||||
public int getEncodingScale() {
|
||||
return EncoderJNI.getEncoderEncodingScale(m_encoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
SendableRegistry.remove(this);
|
||||
// if (m_aSource != null && m_allocatedA) {
|
||||
// m_aSource.close();
|
||||
// m_allocatedA = false;
|
||||
// }
|
||||
// if (m_bSource != null && m_allocatedB) {
|
||||
// m_bSource.close();
|
||||
// m_allocatedB = false;
|
||||
// }
|
||||
// if (m_indexSource != null && m_allocatedI) {
|
||||
// m_indexSource.close();
|
||||
// m_allocatedI = false;
|
||||
// }
|
||||
|
||||
// m_aSource = null;
|
||||
// m_bSource = null;
|
||||
// m_indexSource = null;
|
||||
EncoderJNI.freeEncoder(m_encoder);
|
||||
m_encoder = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw value from the encoder. The raw value is the actual count unscaled by the 1x, 2x,
|
||||
* or 4x scale factor.
|
||||
*
|
||||
* @return Current raw count from the encoder
|
||||
*/
|
||||
public int getRaw() {
|
||||
return EncoderJNI.getEncoderRaw(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current count. Returns the current count on the Encoder. This method compensates for
|
||||
* the decoding type.
|
||||
*
|
||||
* @return Current count from the Encoder adjusted for the 1x, 2x, or 4x scale factor.
|
||||
*/
|
||||
@Override
|
||||
public int get() {
|
||||
return EncoderJNI.getEncoder(m_encoder);
|
||||
}
|
||||
|
||||
/** Reset the Encoder distance to zero. Resets the current count to zero on the encoder. */
|
||||
@Override
|
||||
public void reset() {
|
||||
EncoderJNI.resetEncoder(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the period of the most recent pulse. Returns the period of the most recent Encoder
|
||||
* pulse in seconds. This method compensates for the decoding type.
|
||||
*
|
||||
* <p><b>Warning:</b> This returns unscaled periods. Use getRate() for rates that are scaled using
|
||||
* the value from setDistancePerPulse().
|
||||
*
|
||||
* @return Period in seconds of the most recent pulse.
|
||||
* @deprecated Use getRate() in favor of this method.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public double getPeriod() {
|
||||
return EncoderJNI.getEncoderPeriod(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum period for stopped detection. Sets the value that represents the maximum
|
||||
* period of the Encoder before it will assume that the attached device is stopped. This timeout
|
||||
* allows users to determine if the wheels or other shaft has stopped rotating. This method
|
||||
* compensates for the decoding type.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the encoder is stopped. Using the MaxPeriod value, a boolean is returned that is
|
||||
* true if the encoder is considered stopped and false if it is still moving. A stopped encoder is
|
||||
* one where the most recent pulse width exceeds the MaxPeriod.
|
||||
*
|
||||
* @return True if the encoder is considered stopped.
|
||||
*/
|
||||
@Override
|
||||
public boolean getStopped() {
|
||||
return EncoderJNI.getEncoderStopped(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* The last direction the encoder value changed.
|
||||
*
|
||||
* @return The last direction the encoder value changed.
|
||||
*/
|
||||
@Override
|
||||
public boolean getDirection() {
|
||||
return EncoderJNI.getEncoderDirection(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance the robot has driven since the last reset as scaled by the value from {@link
|
||||
* #setDistancePerPulse(double)}.
|
||||
*
|
||||
* @return The distance driven since the last reset
|
||||
*/
|
||||
public double getDistance() {
|
||||
return EncoderJNI.getEncoderDistance(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current rate of the encoder. Units are distance per second as scaled by the value from
|
||||
* setDistancePerPulse().
|
||||
*
|
||||
* @return The current rate of the encoder.
|
||||
*/
|
||||
public double getRate() {
|
||||
return EncoderJNI.getEncoderRate(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum rate of the device before the hardware reports it stopped.
|
||||
*
|
||||
* @param minRate The minimum rate. The units are in distance per second as scaled by the value
|
||||
* from setDistancePerPulse().
|
||||
*/
|
||||
public void setMinRate(double minRate) {
|
||||
EncoderJNI.setEncoderMinRate(m_encoder, minRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the distance per pulse for this encoder. This sets the multiplier used to determine the
|
||||
* distance driven based on the count value from the encoder. Do not include the decoding type in
|
||||
* this scale. The library already compensates for the decoding type. Set this value based on the
|
||||
* encoder's rated Pulses per Revolution and factor in gearing reductions following the encoder
|
||||
* shaft. This distance can be in any units you like, linear or angular.
|
||||
*
|
||||
* @param distancePerPulse The scale factor that will be used to convert pulses to useful units.
|
||||
*/
|
||||
public void setDistancePerPulse(double distancePerPulse) {
|
||||
EncoderJNI.setEncoderDistancePerPulse(m_encoder, distancePerPulse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance per pulse for this encoder.
|
||||
*
|
||||
* @return The scale factor that will be used to convert pulses to useful units.
|
||||
*/
|
||||
public double getDistancePerPulse() {
|
||||
return EncoderJNI.getEncoderDistancePerPulse(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the direction sensing for this encoder. This sets the direction sensing on the encoder so
|
||||
* that it could count in the correct software direction regardless of the mounting.
|
||||
*
|
||||
* @param reverseDirection true if the encoder direction should be reversed
|
||||
*/
|
||||
public void setReverseDirection(boolean reverseDirection) {
|
||||
EncoderJNI.setEncoderReverseDirection(m_encoder, reverseDirection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Samples to Average which specifies the number of samples of the timer to average when
|
||||
* calculating the period. Perform averaging to account for mechanical imperfections or as
|
||||
* oversampling to increase resolution.
|
||||
*
|
||||
* @param samplesToAverage The number of samples to average from 1 to 127.
|
||||
*/
|
||||
public void setSamplesToAverage(int samplesToAverage) {
|
||||
EncoderJNI.setEncoderSamplesToAverage(m_encoder, samplesToAverage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Samples to Average which specifies the number of samples of the timer to average when
|
||||
* calculating the period. Perform averaging to account for mechanical imperfections or as
|
||||
* oversampling to increase resolution.
|
||||
*
|
||||
* @return SamplesToAverage The number of samples being averaged (from 1 to 127)
|
||||
*/
|
||||
public int getSamplesToAverage() {
|
||||
return EncoderJNI.getEncoderSamplesToAverage(m_encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates this input is used by a simulated device.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
*/
|
||||
public void setSimDevice(SimDevice device) {
|
||||
EncoderJNI.setEncoderSimDevice(m_encoder, device.getNativeHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the decoding scale factor for scaling raw values to full counts.
|
||||
*
|
||||
* @return decoding scale factor
|
||||
*/
|
||||
public double getDecodingScaleFactor() {
|
||||
return switch (m_encodingType) {
|
||||
case k1X -> 1.0;
|
||||
case k2X -> 0.5;
|
||||
case k4X -> 0.25;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSendable(SendableBuilder builder) {
|
||||
if (EncoderJNI.getEncoderEncodingType(m_encoder) == EncodingType.k4X.value) {
|
||||
builder.setSmartDashboardType("Quadrature Encoder");
|
||||
} else {
|
||||
builder.setSmartDashboardType("Encoder");
|
||||
}
|
||||
|
||||
builder.addDoubleProperty("Speed", this::getRate, null);
|
||||
builder.addDoubleProperty("Distance", this::getDistance, null);
|
||||
builder.addDoubleProperty("Distance per Tick", this::getDistancePerPulse, null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user