[hal, wpilib] Switch PCM to be a single object that is allowed to be duplicated (#3475)

Having PCM as a singleton is a problem, as multiple things need to use it, and that gets really ugly. This changes PCM's to be a reference counted object, that can be passed around and constructed from multiple places.

In Java, this is using a map to hold a data store with a ref count, and allocating new objects any time a duplicate is requested.

In C++, this uses a trick constructor to store a PCM instance in the data store itself. This instance can then be passed to base objects using std::shared_ptr's aliasing constructor, which means constructing a solenoid from a PCM is not allocating after the 1st one.

This did require removing sendable from PCM. A compressor class was added back in to act as sendable for the PCM.

After this change is finished, the only change RobotBuilder and Team Code would require is passing a module type to solenoid constructors.

Co-authored-by: sciencewhiz <sciencewhiz@users.noreply.github.com>
This commit is contained in:
Thad House
2021-09-16 18:50:27 -07:00
committed by GitHub
parent 906bfc8464
commit 60ede67abd
43 changed files with 1016 additions and 317 deletions

View File

@@ -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.FRCNetComm.tResourceType;
import edu.wpi.first.hal.HAL;
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;
/**
* Constructs a compressor for a specified module and type.
*
* @param module The module ID to use.
* @param moduleType The module type to use.
*/
public Compressor(int module, PneumaticsModuleType moduleType) {
m_module = PneumaticsBase.getForType(module, moduleType);
boolean allocatedCompressor = false;
boolean successfulCompletion = false;
try {
if (!m_module.reserveCompressor()) {
throw new AllocationException("Compressor already allocated");
}
allocatedCompressor = true;
m_module.setClosedLoopControl(true);
HAL.report(tResourceType.kResourceType_Compressor, module + 1);
SendableRegistry.addLW(this, "Compressor", module);
successfulCompletion = true;
} finally {
if (!successfulCompletion) {
if (allocatedCompressor) {
m_module.unreserveCompressor();
}
m_module.close();
}
}
}
/**
* Constructs a compressor for a default module and specified type.
*
* @param moduleType The module type to use.
*/
public Compressor(PneumaticsModuleType moduleType) {
this(PneumaticsBase.getDefaultForType(moduleType), moduleType);
}
@Override
public void close() {
SendableRegistry.remove(this);
m_module.unreserveCompressor();
m_module.close();
m_module = null;
}
/**
* Start the compressor running in closed loop control mode.
*
* <p>Use the method in cases where you would like to manually stop and start the compressor for
* applications such as conserving battery or making sure that the compressor motor doesn't start
* during critical operations.
*/
public void start() {
setClosedLoopControl(true);
}
/**
* Stop the compressor from running in closed loop control mode.
*
* <p>Use the method in cases where you would like to manually stop and start the compressor for
* applications such as conserving battery or making sure that the compressor motor doesn't start
* during critical operations.
*/
public void stop() {
setClosedLoopControl(false);
}
/**
* Get the status of the compressor.
*
* @return true if the compressor is on
*/
public boolean enabled() {
return m_module.getCompressor();
}
/**
* Get the pressure switch value.
*
* @return true if the pressure is low
*/
public boolean getPressureSwitchValue() {
return m_module.getPressureSwitch();
}
/**
* Get the current being used by the compressor.
*
* @return current consumed by the compressor in amps
*/
public double getCompressorCurrent() {
return m_module.getCompressorCurrent();
}
/**
* Set the PCM in closed loop control mode.
*
* @param on if true sets the compressor to be in closed loop control mode (default)
*/
public void setClosedLoopControl(boolean on) {
m_module.setClosedLoopControl(on);
}
/**
* Gets the current operating mode of the PCM.
*
* @return true if compressor is operating on closed-loop mode
*/
public boolean getClosedLoopControl() {
return m_module.getClosedLoopControl();
}
@Override
public void initSendable(SendableBuilder builder) {
builder.setSmartDashboardType("Compressor");
builder.addBooleanProperty(
"Closed Loop Control", this::getClosedLoopControl, this::setClosedLoopControl);
builder.addBooleanProperty("Enabled", this::enabled, null);
builder.addBooleanProperty("Pressure switch", this::getPressureSwitchValue, null);
}
}

View File

@@ -10,10 +10,10 @@ 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;
import java.util.Objects;
/**
* DoubleSolenoid class for running 2 channels of high voltage Digital Output on the PCM.
* 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.
@@ -34,22 +34,33 @@ public class DoubleSolenoid implements Sendable, AutoCloseable {
private final int m_reverseChannel;
/**
* Constructor.
* Constructs a double solenoid for a default module of a specific module type.
*
* @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 PneumaticsModuleType moduleType, final int forwardChannel, final int reverseChannel) {
this(PneumaticsBase.getDefaultForType(moduleType), moduleType, forwardChannel, reverseChannel);
}
/**
* Constructs a double solenoid for a specified module of a specific module type.
*
* @param module The module of the solenoid module to use.
* @param forwardChannel The forward channel on the module to control (0..7).
* @param reverseChannel The reverse channel on the module to control (0..7).
* @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(PneumaticsBase module, final int forwardChannel, final int reverseChannel) {
m_module = Objects.requireNonNull(module, "Module cannot be null");
if (!module.checkSolenoidChannel(forwardChannel)) {
throw new IllegalArgumentException("Channel " + forwardChannel + " out of range");
}
if (!module.checkSolenoidChannel(reverseChannel)) {
throw new IllegalArgumentException("Channel " + reverseChannel + " out of range");
}
public DoubleSolenoid(
final int module,
final PneumaticsModuleType moduleType,
final int forwardChannel,
final int reverseChannel) {
m_module = PneumaticsBase.getForType(module, moduleType);
boolean allocatedSolenoids = false;
boolean successfulCompletion = false;
m_forwardChannel = forwardChannel;
m_reverseChannel = reverseChannel;
@@ -58,29 +69,49 @@ public class DoubleSolenoid implements Sendable, AutoCloseable {
m_reverseMask = 1 << reverseChannel;
m_mask = m_forwardMask | m_reverseMask;
int allocMask = 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");
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;
HAL.report(
tResourceType.kResourceType_Solenoid, forwardChannel + 1, m_module.getModuleNumber() + 1);
HAL.report(
tResourceType.kResourceType_Solenoid, reverseChannel + 1, m_module.getModuleNumber() + 1);
SendableRegistry.addLW(this, "DoubleSolenoid", m_module.getModuleNumber(), forwardChannel);
successfulCompletion = true;
} finally {
if (!successfulCompletion) {
if (allocatedSolenoids) {
m_module.unreserveSolenoids(m_mask);
}
m_module.close();
}
}
HAL.report(
tResourceType.kResourceType_Solenoid, forwardChannel + 1, module.getModuleNumber() + 1);
HAL.report(
tResourceType.kResourceType_Solenoid, reverseChannel + 1, module.getModuleNumber() + 1);
SendableRegistry.addLW(this, "DoubleSolenoid", module.getModuleNumber(), forwardChannel);
}
@Override
public synchronized void close() {
SendableRegistry.remove(this);
m_module.unreserveSolenoids(m_mask);
m_module.close();
m_module = null;
}

View File

@@ -5,6 +5,33 @@
package edu.wpi.first.wpilibj;
public interface PneumaticsBase extends AutoCloseable {
/**
* For internal use to get a module for a specific type.
*
* @param module module number
* @param type module type
* @return module
*/
static PneumaticsControlModule getForType(int module, PneumaticsModuleType type) {
if (type == PneumaticsModuleType.CTREPCM) {
return new PneumaticsControlModule();
}
throw new IllegalArgumentException("Unknown module type");
}
/**
* For internal use to get the default for a specific type.
*
* @param type module type
* @return module default
*/
static int getDefaultForType(PneumaticsModuleType type) {
if (type == PneumaticsModuleType.CTREPCM) {
return SensorUtil.getDefaultCTREPCMModule();
}
throw new IllegalArgumentException("Unknown module type");
}
/**
* Sets solenoids on a pneumatics module.
*
@@ -49,6 +76,16 @@ public interface PneumaticsBase extends AutoCloseable {
*/
void setOneShotDuration(int index, int durMs);
boolean getCompressor();
boolean getPressureSwitch();
double getCompressorCurrent();
void setClosedLoopControl(boolean on);
boolean getClosedLoopControl();
/**
* Check if a solenoid channel is valid.
*
@@ -71,4 +108,17 @@ public interface PneumaticsBase extends AutoCloseable {
* @param mask The solenoid mask to unreserve
*/
void unreserveSolenoids(int mask);
boolean reserveCompressor();
void unreserveCompressor();
@Override
void close();
Solenoid makeSolenoid(int channel);
DoubleSolenoid makeDoubleSolenoid(int forwardChannel, int reverseChannel);
Compressor makeCompressor();
}

View File

@@ -5,16 +5,68 @@
package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.CTREPCMJNI;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
import java.util.HashMap;
import java.util.Map;
public class PneumaticsControlModule implements PneumaticsBase, Sendable {
/** 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 int m_refCount;
private int m_reservedMask;
private boolean m_compressorReserved;
private final Object m_reserveLock = new Object();
DataStore(int module) {
m_handle = CTREPCMJNI.initialize(module);
m_module = module;
m_handleMap.put(module, this);
}
@Override
public void close() {
CTREPCMJNI.free(m_handle);
m_handleMap.remove(m_module);
}
public void addRef() {
m_refCount++;
}
public void removeRef() {
m_refCount--;
if (m_refCount == 0) {
this.close();
}
}
}
private static final Map<Integer, DataStore> m_handleMap = new HashMap<>();
private static final Object m_handleLock = new Object();
private static DataStore getForModule(int module) {
synchronized (m_handleLock) {
Integer moduleBoxed = module;
DataStore pcm = m_handleMap.get(moduleBoxed);
if (pcm == null) {
pcm = new DataStore(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;
private final int m_module;
private int m_reservedMask;
private final Object m_reserveLock = new Object();
/** Constructs a PneumaticsControlModule with the default id (0). */
public PneumaticsControlModule() {
this(SensorUtil.getDefaultCTREPCMModule());
}
@@ -25,34 +77,36 @@ public class PneumaticsControlModule implements PneumaticsBase, Sendable {
* @param module module number to construct
*/
public PneumaticsControlModule(int module) {
m_handle = CTREPCMJNI.initialize(module);
m_module = module;
SendableRegistry.addLW(this, "Compressor", module);
m_dataStore = getForModule(module);
m_handle = m_dataStore.m_handle;
}
@Override
public void close() {
CTREPCMJNI.free(m_handle);
SendableRegistry.remove(this);
freeModule(m_dataStore);
}
@Override
public boolean getCompressor() {
return CTREPCMJNI.getCompressor(m_handle);
}
@Override
public void setClosedLoopControl(boolean enabled) {
CTREPCMJNI.setClosedLoopControl(m_handle, enabled);
}
@Override
public boolean getClosedLoopControl() {
return CTREPCMJNI.getClosedLoopControl(m_handle);
}
@Override
public boolean getPressureSwitch() {
return CTREPCMJNI.getPressureSwitch(m_handle);
}
@Override
public double getCompressorCurrent() {
return CTREPCMJNI.getCompressorCurrent(m_handle);
}
@@ -93,7 +147,7 @@ public class PneumaticsControlModule implements PneumaticsBase, Sendable {
@Override
public int getModuleNumber() {
return m_module;
return m_dataStore.m_handle;
}
@Override
@@ -130,28 +184,53 @@ public class PneumaticsControlModule implements PneumaticsBase, Sendable {
@Override
public int checkAndReserveSolenoids(int mask) {
synchronized (m_reserveLock) {
if ((m_reservedMask & mask) != 0) {
return m_reservedMask & mask;
synchronized (m_dataStore.m_reserveLock) {
if ((m_dataStore.m_reservedMask & mask) != 0) {
return m_dataStore.m_reservedMask & mask;
}
m_reservedMask |= mask;
m_dataStore.m_reservedMask |= mask;
return 0;
}
}
@Override
public void unreserveSolenoids(int mask) {
synchronized (m_reserveLock) {
m_reservedMask &= ~mask;
synchronized (m_dataStore.m_reserveLock) {
m_dataStore.m_reservedMask &= ~mask;
}
}
@Override
public void initSendable(SendableBuilder builder) {
builder.setSmartDashboardType("Compressor");
builder.addBooleanProperty(
"Closed Loop Control", this::getClosedLoopControl, this::setClosedLoopControl);
builder.addBooleanProperty("Enabled", this::getCompressor, null);
builder.addBooleanProperty("Pressure switch", this::getPressureSwitch, null);
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;
}
}
}

View File

@@ -0,0 +1,10 @@
// 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;
public enum PneumaticsModuleType {
CTREPCM,
REVPH;
}

View File

@@ -10,13 +10,12 @@ 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;
import java.util.Objects;
/**
* Solenoid class for running high voltage Digital Output on the PCM.
* 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 PCM.
* within the current spec of the module.
*/
public class Solenoid implements Sendable, AutoCloseable {
private final int m_mask; // The channel mask
@@ -24,33 +23,60 @@ public class Solenoid implements Sendable, AutoCloseable {
private PneumaticsBase m_module;
/**
* Constructor.
* Constructs a solenoid for a default module and specified type.
*
* @param module The PCM the solenoid is attached to.
* @param channel The channel on the PCM to control (0..7).
* @param moduleType The module type to use.
* @param channel The channel the solenoid is on.
*/
public Solenoid(PneumaticsBase module, final int channel) {
m_module = Objects.requireNonNull(module, "Module cannot be null");
public Solenoid(final PneumaticsModuleType moduleType, final int channel) {
this(PneumaticsBase.getDefaultForType(moduleType), moduleType, channel);
}
if (!module.checkSolenoidChannel(channel)) {
throw new IllegalArgumentException(); // TODO fix me
}
/**
* Constructs a solenoid for a specified module and type.
*
* @param module The module ID to use.
* @param moduleType The module type to use.
* @param channel The channel the solenoid is on.
*/
public Solenoid(final int module, final PneumaticsModuleType moduleType, final int channel) {
m_module = PneumaticsBase.getForType(module, moduleType);
boolean allocatedSolenoids = false;
boolean successfulCompletion = false;
m_mask = 1 << channel;
m_channel = channel;
if (module.checkAndReserveSolenoids(m_mask) != 0) {
throw new AllocationException("Solenoid already allocated");
}
try {
if (!m_module.checkSolenoidChannel(channel)) {
throw new IllegalArgumentException("Channel " + channel + " out of range");
}
HAL.report(tResourceType.kResourceType_Solenoid, channel + 1, module.getModuleNumber() + 1);
SendableRegistry.addLW(this, "Solenoid", module.getModuleNumber(), channel);
if (m_module.checkAndReserveSolenoids(m_mask) != 0) {
throw new AllocationException("Solenoid already allocated");
}
allocatedSolenoids = true;
HAL.report(tResourceType.kResourceType_Solenoid, channel + 1, m_module.getModuleNumber() + 1);
SendableRegistry.addLW(this, "Solenoid", m_module.getModuleNumber(), channel);
successfulCompletion = true;
} finally {
if (!successfulCompletion) {
if (allocatedSolenoids) {
m_module.unreserveSolenoids(m_mask);
}
m_module.close();
}
}
}
@Override
public void close() {
SendableRegistry.remove(this);
m_module.unreserveSolenoids(m_mask);
m_module.close();
m_module = null;
}