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