001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package org.wpilib.hardware.pneumatic; 006 007import java.util.HashMap; 008import java.util.Map; 009import org.wpilib.hardware.hal.HAL; 010import org.wpilib.hardware.hal.PortsJNI; 011import org.wpilib.hardware.hal.REVPHFaults; 012import org.wpilib.hardware.hal.REVPHJNI; 013import org.wpilib.hardware.hal.REVPHStickyFaults; 014import org.wpilib.hardware.hal.REVPHVersion; 015import org.wpilib.system.SensorUtil; 016 017/** Module class for controlling a REV Robotics Pneumatic Hub. */ 018public class PneumaticHub implements PneumaticsBase { 019 private static class DataStore implements AutoCloseable { 020 public final int m_module; 021 public final int m_handle; 022 private final int m_busId; 023 private int m_refCount; 024 private int m_reservedMask; 025 private boolean m_compressorReserved; 026 public final int[] m_oneShotDurMs = new int[PortsJNI.getNumREVPHChannels()]; 027 private final Object m_reserveLock = new Object(); 028 029 DataStore(int busId, int module) { 030 m_handle = REVPHJNI.initialize(busId, module); 031 m_module = module; 032 m_busId = busId; 033 m_handleMaps[busId].put(module, this); 034 035 final REVPHVersion version = REVPHJNI.getVersion(m_handle); 036 final String fwVersion = 037 version.firmwareMajor + "." + version.firmwareMinor + "." + version.firmwareFix; 038 039 // Check PH firmware version 040 if (version.firmwareMajor > 0 && version.firmwareMajor < 22) { 041 throw new IllegalStateException( 042 "The Pneumatic Hub has firmware version " 043 + fwVersion 044 + ", and must be updated to version 2022.0.0 or later " 045 + "using the REV Hardware Client."); 046 } 047 } 048 049 @Override 050 public void close() { 051 REVPHJNI.free(m_handle); 052 m_handleMaps[m_busId].remove(m_module); 053 } 054 055 public void addRef() { 056 m_refCount++; 057 } 058 059 public void removeRef() { 060 m_refCount--; 061 if (m_refCount == 0) { 062 this.close(); 063 } 064 } 065 } 066 067 @SuppressWarnings({"unchecked", "rawtypes"}) 068 private static final Map<Integer, DataStore>[] m_handleMaps = 069 (Map<Integer, DataStore>[]) new Map[PortsJNI.getNumCanBuses()]; 070 071 private static final Object m_handleLock = new Object(); 072 073 private static DataStore getForModule(int busId, int module) { 074 synchronized (m_handleLock) { 075 Map<Integer, DataStore> handleMap = m_handleMaps[busId]; 076 if (handleMap == null) { 077 handleMap = new HashMap<>(); 078 m_handleMaps[busId] = handleMap; 079 } 080 081 DataStore pcm = handleMap.get(module); 082 if (pcm == null) { 083 pcm = new DataStore(busId, module); 084 } 085 pcm.addRef(); 086 return pcm; 087 } 088 } 089 090 private static void freeModule(DataStore store) { 091 synchronized (m_handleLock) { 092 store.removeRef(); 093 } 094 } 095 096 /** Converts volts to PSI per the REV Analog Pressure Sensor datasheet. */ 097 private static double voltsToPsi(double sensorVoltage, double supplyVoltage) { 098 return 250 * (sensorVoltage / supplyVoltage) - 25; 099 } 100 101 /** Converts PSI to volts per the REV Analog Pressure Sensor datasheet. */ 102 private static double psiToVolts(double pressure, double supplyVoltage) { 103 return supplyVoltage * (0.004 * pressure + 0.1); 104 } 105 106 private final DataStore m_dataStore; 107 private final int m_handle; 108 109 /** 110 * Constructs a PneumaticHub with the default ID (1). 111 * 112 * @param busId The bus ID 113 */ 114 public PneumaticHub(int busId) { 115 this(busId, SensorUtil.getDefaultREVPHModule()); 116 } 117 118 /** 119 * Constructs a PneumaticHub. 120 * 121 * @param busId The bus ID 122 * @param module module number to construct 123 */ 124 public PneumaticHub(int busId, int module) { 125 m_dataStore = getForModule(busId, module); 126 m_handle = m_dataStore.m_handle; 127 } 128 129 @Override 130 public void close() { 131 freeModule(m_dataStore); 132 } 133 134 @Override 135 public boolean getCompressor() { 136 return REVPHJNI.getCompressor(m_handle); 137 } 138 139 @Override 140 public CompressorConfigType getCompressorConfigType() { 141 return CompressorConfigType.fromValue(REVPHJNI.getCompressorConfig(m_handle)); 142 } 143 144 @Override 145 public boolean getPressureSwitch() { 146 return REVPHJNI.getPressureSwitch(m_handle); 147 } 148 149 @Override 150 public double getCompressorCurrent() { 151 return REVPHJNI.getCompressorCurrent(m_handle); 152 } 153 154 @Override 155 public void setSolenoids(int mask, int values) { 156 REVPHJNI.setSolenoids(m_handle, mask, values); 157 } 158 159 @Override 160 public int getSolenoids() { 161 return REVPHJNI.getSolenoids(m_handle); 162 } 163 164 @Override 165 public int getModuleNumber() { 166 return m_dataStore.m_module; 167 } 168 169 @Override 170 public void fireOneShot(int index) { 171 REVPHJNI.fireOneShot(m_handle, index, m_dataStore.m_oneShotDurMs[index]); 172 } 173 174 @Override 175 public void setOneShotDuration(int index, int durMs) { 176 m_dataStore.m_oneShotDurMs[index] = durMs; 177 } 178 179 @Override 180 public boolean checkSolenoidChannel(int channel) { 181 return REVPHJNI.checkSolenoidChannel(channel); 182 } 183 184 @Override 185 public int checkAndReserveSolenoids(int mask) { 186 synchronized (m_dataStore.m_reserveLock) { 187 if ((m_dataStore.m_reservedMask & mask) != 0) { 188 return m_dataStore.m_reservedMask & mask; 189 } 190 m_dataStore.m_reservedMask |= mask; 191 return 0; 192 } 193 } 194 195 @Override 196 public void unreserveSolenoids(int mask) { 197 synchronized (m_dataStore.m_reserveLock) { 198 m_dataStore.m_reservedMask &= ~mask; 199 } 200 } 201 202 @Override 203 public Solenoid makeSolenoid(int channel) { 204 return new Solenoid(m_dataStore.m_module, PneumaticsModuleType.REVPH, channel); 205 } 206 207 @Override 208 public DoubleSolenoid makeDoubleSolenoid(int forwardChannel, int reverseChannel) { 209 return new DoubleSolenoid( 210 m_dataStore.m_module, PneumaticsModuleType.REVPH, forwardChannel, reverseChannel); 211 } 212 213 @Override 214 public Compressor makeCompressor() { 215 return new Compressor(m_dataStore.m_module, PneumaticsModuleType.REVPH); 216 } 217 218 @Override 219 public boolean reserveCompressor() { 220 synchronized (m_dataStore.m_reserveLock) { 221 if (m_dataStore.m_compressorReserved) { 222 return false; 223 } 224 m_dataStore.m_compressorReserved = true; 225 return true; 226 } 227 } 228 229 @Override 230 public void unreserveCompressor() { 231 synchronized (m_dataStore.m_reserveLock) { 232 m_dataStore.m_compressorReserved = false; 233 } 234 } 235 236 @Override 237 public int getSolenoidDisabledList() { 238 return REVPHJNI.getSolenoidDisabledList(m_handle); 239 } 240 241 /** 242 * Disables the compressor. The compressor will not turn on until {@link 243 * #enableCompressorDigital()}, {@link #enableCompressorAnalog(double, double)}, or {@link 244 * #enableCompressorHybrid(double, double)} are called. 245 */ 246 @Override 247 public void disableCompressor() { 248 REVPHJNI.setClosedLoopControlDisabled(m_handle); 249 } 250 251 @Override 252 public void enableCompressorDigital() { 253 REVPHJNI.setClosedLoopControlDigital(m_handle); 254 } 255 256 /** 257 * Enables the compressor in analog mode. This mode uses an analog pressure sensor connected to 258 * analog channel 0 to cycle the compressor. The compressor will turn on when the pressure drops 259 * below {@code minPressure} and will turn off when the pressure reaches {@code maxPressure}. 260 * 261 * @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure 262 * drops below this value. Range 0-120 PSI. 263 * @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure 264 * reaches this value. Range 0-120 PSI. Must be larger then minPressure. 265 */ 266 @Override 267 public void enableCompressorAnalog(double minPressure, double maxPressure) { 268 if (minPressure >= maxPressure) { 269 throw new IllegalArgumentException("maxPressure must be greater than minPressure"); 270 } 271 if (minPressure < 0 || minPressure > 120) { 272 throw new IllegalArgumentException( 273 "minPressure must be between 0 and 120 PSI, got " + minPressure); 274 } 275 if (maxPressure < 0 || maxPressure > 120) { 276 throw new IllegalArgumentException( 277 "maxPressure must be between 0 and 120 PSI, got " + maxPressure); 278 } 279 280 // Send the voltage as it would be if the 5V rail was at exactly 5V. 281 // The firmware will compensate for the real 5V rail voltage, which 282 // can fluctuate somewhat over time. 283 double minAnalogVoltage = psiToVolts(minPressure, 5); 284 double maxAnalogVoltage = psiToVolts(maxPressure, 5); 285 REVPHJNI.setClosedLoopControlAnalog(m_handle, minAnalogVoltage, maxAnalogVoltage); 286 } 287 288 /** 289 * Enables the compressor in hybrid mode. This mode uses both a digital pressure switch and an 290 * analog pressure sensor connected to analog channel 0 to cycle the compressor. 291 * 292 * <p>The compressor will turn on when <i>both</i>: 293 * 294 * <ul> 295 * <li>The digital pressure switch indicates the system is not full AND 296 * <li>The analog pressure sensor indicates that the pressure in the system is below the 297 * specified minimum pressure. 298 * </ul> 299 * 300 * <p>The compressor will turn off when <i>either</i>: 301 * 302 * <ul> 303 * <li>The digital pressure switch is disconnected or indicates that the system is full OR 304 * <li>The pressure detected by the analog sensor is greater than the specified maximum 305 * pressure. 306 * </ul> 307 * 308 * @param minPressure The minimum pressure in PSI. The compressor will turn on when the pressure 309 * drops below this value and the pressure switch indicates that the system is not full. Range 310 * 0-120 PSI. 311 * @param maxPressure The maximum pressure in PSI. The compressor will turn off when the pressure 312 * reaches this value or the pressure switch is disconnected or indicates that the system is 313 * full. Range 0-120 PSI. Must be larger then minPressure. 314 */ 315 @Override 316 public void enableCompressorHybrid(double minPressure, double maxPressure) { 317 if (minPressure >= maxPressure) { 318 throw new IllegalArgumentException("maxPressure must be greater than minPressure"); 319 } 320 if (minPressure < 0 || minPressure > 120) { 321 throw new IllegalArgumentException( 322 "minPressure must be between 0 and 120 PSI, got " + minPressure); 323 } 324 if (maxPressure < 0 || maxPressure > 120) { 325 throw new IllegalArgumentException( 326 "maxPressure must be between 0 and 120 PSI, got " + maxPressure); 327 } 328 329 // Send the voltage as it would be if the 5V rail was at exactly 5V. 330 // The firmware will compensate for the real 5V rail voltage, which 331 // can fluctuate somewhat over time. 332 double minAnalogVoltage = psiToVolts(minPressure, 5); 333 double maxAnalogVoltage = psiToVolts(maxPressure, 5); 334 REVPHJNI.setClosedLoopControlHybrid(m_handle, minAnalogVoltage, maxAnalogVoltage); 335 } 336 337 /** 338 * Returns the raw voltage of the specified analog input channel. 339 * 340 * @param channel The analog input channel to read voltage from. 341 * @return The voltage of the specified analog input channel. 342 */ 343 @Override 344 public double getAnalogVoltage(int channel) { 345 return REVPHJNI.getAnalogVoltage(m_handle, channel); 346 } 347 348 /** 349 * Returns the pressure read by an analog pressure sensor on the specified analog input channel. 350 * 351 * @param channel The analog input channel to read pressure from. 352 * @return The pressure read by an analog pressure sensor on the specified analog input channel. 353 */ 354 @Override 355 public double getPressure(int channel) { 356 double sensorVoltage = REVPHJNI.getAnalogVoltage(m_handle, channel); 357 double supplyVoltage = REVPHJNI.get5VVoltage(m_handle); 358 return voltsToPsi(sensorVoltage, supplyVoltage); 359 } 360 361 /** Clears the sticky faults. */ 362 public void clearStickyFaults() { 363 REVPHJNI.clearStickyFaults(m_handle); 364 } 365 366 /** 367 * Returns the hardware and firmware versions of this device. 368 * 369 * @return The hardware and firmware versions. 370 */ 371 public REVPHVersion getVersion() { 372 return REVPHJNI.getVersion(m_handle); 373 } 374 375 /** 376 * Returns the faults currently active on this device. 377 * 378 * @return The faults. 379 */ 380 public REVPHFaults getFaults() { 381 return REVPHJNI.getFaults(m_handle); 382 } 383 384 /** 385 * Returns the sticky faults currently active on this device. 386 * 387 * @return The sticky faults. 388 */ 389 public REVPHStickyFaults getStickyFaults() { 390 return REVPHJNI.getStickyFaults(m_handle); 391 } 392 393 /** 394 * Returns the current input voltage for this device. 395 * 396 * @return The input voltage. 397 */ 398 public double getInputVoltage() { 399 return REVPHJNI.getInputVoltage(m_handle); 400 } 401 402 /** 403 * Returns the current voltage of the regulated 5v supply. 404 * 405 * @return The current voltage of the 5v supply. 406 */ 407 public double get5VRegulatedVoltage() { 408 return REVPHJNI.get5VVoltage(m_handle); 409 } 410 411 /** 412 * Returns the total current (in amps) drawn by all solenoids. 413 * 414 * @return Total current drawn by all solenoids in amps. 415 */ 416 public double getSolenoidsTotalCurrent() { 417 return REVPHJNI.getSolenoidCurrent(m_handle); 418 } 419 420 /** 421 * Returns the current voltage of the solenoid power supply. 422 * 423 * @return The current voltage of the solenoid power supply. 424 */ 425 public double getSolenoidsVoltage() { 426 return REVPHJNI.getSolenoidVoltage(m_handle); 427 } 428 429 @Override 430 public void reportUsage(String device, String data) { 431 HAL.reportUsage("PH[" + m_dataStore.m_module + "]/" + device, data); 432 } 433}