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