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.CTREPCMJNI;
010import org.wpilib.hardware.hal.HAL;
011import org.wpilib.hardware.hal.PortsJNI;
012import org.wpilib.system.SensorUtil;
013
014/** Module class for controlling a Cross The Road Electronics Pneumatics Control Module. */
015public class PneumaticsControlModule implements PneumaticsBase {
016  private static class DataStore implements AutoCloseable {
017    public final int m_module;
018    public final int m_handle;
019    private final int m_busId;
020    private int m_refCount;
021    private int m_reservedMask;
022    private boolean m_compressorReserved;
023    private final Object m_reserveLock = new Object();
024
025    DataStore(int busId, int module) {
026      m_handle = CTREPCMJNI.initialize(busId, module);
027      m_module = module;
028      m_busId = busId;
029      m_handleMaps[busId].put(module, this);
030    }
031
032    @Override
033    public void close() {
034      CTREPCMJNI.free(m_handle);
035      m_handleMaps[m_busId].remove(m_module);
036    }
037
038    public void addRef() {
039      m_refCount++;
040    }
041
042    public void removeRef() {
043      m_refCount--;
044      if (m_refCount == 0) {
045        this.close();
046      }
047    }
048  }
049
050  @SuppressWarnings({"unchecked", "rawtypes"})
051  private static final Map<Integer, DataStore>[] m_handleMaps =
052      (Map<Integer, DataStore>[]) new Map[PortsJNI.getNumCanBuses()];
053
054  private static final Object m_handleLock = new Object();
055
056  private static DataStore getForModule(int busId, int module) {
057    synchronized (m_handleLock) {
058      Map<Integer, DataStore> handleMap = m_handleMaps[busId];
059      if (handleMap == null) {
060        handleMap = new HashMap<>();
061        m_handleMaps[busId] = handleMap;
062      }
063
064      DataStore pcm = handleMap.get(module);
065      if (pcm == null) {
066        pcm = new DataStore(busId, module);
067      }
068      pcm.addRef();
069      return pcm;
070    }
071  }
072
073  private static void freeModule(DataStore store) {
074    synchronized (m_handleLock) {
075      store.removeRef();
076    }
077  }
078
079  private final DataStore m_dataStore;
080  private final int m_handle;
081
082  /**
083   * Constructs a PneumaticsControlModule with the default ID (0).
084   *
085   * @param busId The bus ID
086   */
087  public PneumaticsControlModule(int busId) {
088    this(busId, SensorUtil.getDefaultCTREPCMModule());
089  }
090
091  /**
092   * Constructs a PneumaticsControlModule.
093   *
094   * @param busId The bus ID
095   * @param module module number to construct
096   */
097  public PneumaticsControlModule(int busId, int module) {
098    m_dataStore = getForModule(busId, module);
099    m_handle = m_dataStore.m_handle;
100  }
101
102  @Override
103  public void close() {
104    freeModule(m_dataStore);
105  }
106
107  @Override
108  public boolean getCompressor() {
109    return CTREPCMJNI.getCompressor(m_handle);
110  }
111
112  @Override
113  public boolean getPressureSwitch() {
114    return CTREPCMJNI.getPressureSwitch(m_handle);
115  }
116
117  @Override
118  public double getCompressorCurrent() {
119    return CTREPCMJNI.getCompressorCurrent(m_handle);
120  }
121
122  /**
123   * Return whether the compressor current is currently too high.
124   *
125   * @return True if the compressor current is too high, otherwise false.
126   * @see #getCompressorCurrentTooHighStickyFault()
127   */
128  public boolean getCompressorCurrentTooHighFault() {
129    return CTREPCMJNI.getCompressorCurrentTooHighFault(m_handle);
130  }
131
132  /**
133   * Returns whether the compressor current has been too high since sticky faults were last cleared.
134   * This fault is persistent and can be cleared by {@link #clearAllStickyFaults()}
135   *
136   * @return True if the compressor current has been too high since sticky faults were last cleared.
137   * @see #getCompressorCurrentTooHighFault()
138   */
139  public boolean getCompressorCurrentTooHighStickyFault() {
140    return CTREPCMJNI.getCompressorCurrentTooHighStickyFault(m_handle);
141  }
142
143  /**
144   * Returns whether the compressor is currently shorted.
145   *
146   * @return True if the compressor is currently shorted, otherwise false.
147   * @see #getCompressorShortedStickyFault()
148   */
149  public boolean getCompressorShortedFault() {
150    return CTREPCMJNI.getCompressorShortedFault(m_handle);
151  }
152
153  /**
154   * Returns whether the compressor has been shorted since sticky faults were last cleared. This
155   * fault is persistent and can be cleared by {@link #clearAllStickyFaults()}
156   *
157   * @return True if the compressor has been shorted since sticky faults were last cleared,
158   *     otherwise false.
159   * @see #getCompressorShortedFault()
160   */
161  public boolean getCompressorShortedStickyFault() {
162    return CTREPCMJNI.getCompressorShortedStickyFault(m_handle);
163  }
164
165  /**
166   * Returns whether the compressor is currently disconnected.
167   *
168   * @return True if compressor is currently disconnected, otherwise false.
169   * @see #getCompressorNotConnectedStickyFault()
170   */
171  public boolean getCompressorNotConnectedFault() {
172    return CTREPCMJNI.getCompressorNotConnectedFault(m_handle);
173  }
174
175  /**
176   * Returns whether the compressor has been disconnected since sticky faults were last cleared.
177   * This fault is persistent and can be cleared by {@link #clearAllStickyFaults()}
178   *
179   * @return True if the compressor has been disconnected since sticky faults were last cleared,
180   *     otherwise false.
181   * @see #getCompressorNotConnectedFault()
182   */
183  public boolean getCompressorNotConnectedStickyFault() {
184    return CTREPCMJNI.getCompressorNotConnectedStickyFault(m_handle);
185  }
186
187  @Override
188  public void setSolenoids(int mask, int values) {
189    CTREPCMJNI.setSolenoids(m_handle, mask, values);
190  }
191
192  @Override
193  public int getSolenoids() {
194    return CTREPCMJNI.getSolenoids(m_handle);
195  }
196
197  @Override
198  public int getModuleNumber() {
199    return m_dataStore.m_module;
200  }
201
202  @Override
203  public int getSolenoidDisabledList() {
204    return CTREPCMJNI.getSolenoidDisabledList(m_handle);
205  }
206
207  /**
208   * Returns whether the solenoid is currently reporting a voltage fault.
209   *
210   * @return True if solenoid is reporting a fault, otherwise false.
211   * @see #getSolenoidVoltageStickyFault()
212   */
213  public boolean getSolenoidVoltageFault() {
214    return CTREPCMJNI.getSolenoidVoltageFault(m_handle);
215  }
216
217  /**
218   * Returns whether the solenoid has reported a voltage fault since sticky faults were last
219   * cleared. This fault is persistent and can be cleared by ClearAllStickyFaults()
220   *
221   * @return True if solenoid is reporting a fault, otherwise false.
222   * @see #getSolenoidVoltageFault()
223   */
224  public boolean getSolenoidVoltageStickyFault() {
225    return CTREPCMJNI.getSolenoidVoltageStickyFault(m_handle);
226  }
227
228  /** Clears all sticky faults on this device. */
229  public void clearAllStickyFaults() {
230    CTREPCMJNI.clearAllStickyFaults(m_handle);
231  }
232
233  @Override
234  public void fireOneShot(int index) {
235    CTREPCMJNI.fireOneShot(m_handle, index);
236  }
237
238  @Override
239  public void setOneShotDuration(int index, int durMs) {
240    CTREPCMJNI.setOneShotDuration(m_handle, index, durMs);
241  }
242
243  @Override
244  public boolean checkSolenoidChannel(int channel) {
245    return CTREPCMJNI.checkSolenoidChannel(channel);
246  }
247
248  @Override
249  public int checkAndReserveSolenoids(int mask) {
250    synchronized (m_dataStore.m_reserveLock) {
251      if ((m_dataStore.m_reservedMask & mask) != 0) {
252        return m_dataStore.m_reservedMask & mask;
253      }
254      m_dataStore.m_reservedMask |= mask;
255      return 0;
256    }
257  }
258
259  @Override
260  public void unreserveSolenoids(int mask) {
261    synchronized (m_dataStore.m_reserveLock) {
262      m_dataStore.m_reservedMask &= ~mask;
263    }
264  }
265
266  @Override
267  public Solenoid makeSolenoid(int channel) {
268    return new Solenoid(m_dataStore.m_module, PneumaticsModuleType.CTREPCM, channel);
269  }
270
271  @Override
272  public DoubleSolenoid makeDoubleSolenoid(int forwardChannel, int reverseChannel) {
273    return new DoubleSolenoid(
274        m_dataStore.m_module, PneumaticsModuleType.CTREPCM, forwardChannel, reverseChannel);
275  }
276
277  @Override
278  public Compressor makeCompressor() {
279    return new Compressor(m_dataStore.m_module, PneumaticsModuleType.CTREPCM);
280  }
281
282  @Override
283  public boolean reserveCompressor() {
284    synchronized (m_dataStore.m_reserveLock) {
285      if (m_dataStore.m_compressorReserved) {
286        return false;
287      }
288      m_dataStore.m_compressorReserved = true;
289      return true;
290    }
291  }
292
293  @Override
294  public void unreserveCompressor() {
295    synchronized (m_dataStore.m_reserveLock) {
296      m_dataStore.m_compressorReserved = false;
297    }
298  }
299
300  /**
301   * Disables the compressor. The compressor will not turn on until {@link
302   * #enableCompressorDigital()} is called.
303   */
304  @Override
305  public void disableCompressor() {
306    CTREPCMJNI.setClosedLoopControl(m_handle, false);
307  }
308
309  @Override
310  public void enableCompressorDigital() {
311    CTREPCMJNI.setClosedLoopControl(m_handle, true);
312  }
313
314  /**
315   * Enables the compressor in digital mode. Analog mode is unsupported by the CTRE PCM.
316   *
317   * @param minPressure Unsupported.
318   * @param maxPressure Unsupported.
319   * @see #enableCompressorDigital()
320   */
321  @Override
322  public void enableCompressorAnalog(double minPressure, double maxPressure) {
323    CTREPCMJNI.setClosedLoopControl(m_handle, false);
324  }
325
326  /**
327   * Enables the compressor in digital mode. Hybrid mode is unsupported by the CTRE PCM.
328   *
329   * @param minPressure Unsupported.
330   * @param maxPressure Unsupported.
331   * @see #enableCompressorDigital()
332   */
333  @Override
334  public void enableCompressorHybrid(double minPressure, double maxPressure) {
335    CTREPCMJNI.setClosedLoopControl(m_handle, false);
336  }
337
338  @Override
339  public CompressorConfigType getCompressorConfigType() {
340    return CTREPCMJNI.getClosedLoopControl(m_handle)
341        ? CompressorConfigType.Digital
342        : CompressorConfigType.Disabled;
343  }
344
345  /**
346   * Unsupported by the CTRE PCM.
347   *
348   * @param channel Unsupported.
349   * @return 0
350   */
351  @Override
352  public double getAnalogVoltage(int channel) {
353    return 0;
354  }
355
356  /**
357   * Unsupported by the CTRE PCM.
358   *
359   * @param channel Unsupported.
360   * @return 0
361   */
362  @Override
363  public double getPressure(int channel) {
364    return 0;
365  }
366
367  @Override
368  public void reportUsage(String device, String data) {
369    HAL.reportUsage("PCM[" + m_dataStore.m_module + "]/" + device, data);
370  }
371}