001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-2017. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj;
009
010import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
011import edu.wpi.first.wpilibj.hal.HAL;
012import edu.wpi.first.wpilibj.hal.SolenoidJNI;
013import edu.wpi.first.wpilibj.livewindow.LiveWindow;
014import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
015import edu.wpi.first.wpilibj.tables.ITable;
016import edu.wpi.first.wpilibj.tables.ITableListener;
017
018/**
019 * DoubleSolenoid class for running 2 channels of high voltage Digital Output on the PCM.
020 *
021 * <p>The DoubleSolenoid class is typically used for pneumatics solenoids that have two positions
022 * controlled by two separate channels.
023 */
024public class DoubleSolenoid extends SolenoidBase implements LiveWindowSendable {
025
026  /**
027   * Possible values for a DoubleSolenoid.
028   */
029  public enum Value {
030    kOff,
031    kForward,
032    kReverse
033  }
034
035  private byte m_forwardMask; // The mask for the forward channel.
036  private byte m_reverseMask; // The mask for the reverse channel.
037  private int m_forwardHandle = 0;
038  private int m_reverseHandle = 0;
039
040  /**
041   * Constructor. Uses the default PCM ID (defaults to 0).
042   *
043   * @param forwardChannel The forward channel number on the PCM (0..7).
044   * @param reverseChannel The reverse channel number on the PCM (0..7).
045   */
046  public DoubleSolenoid(final int forwardChannel, final int reverseChannel) {
047    this(getDefaultSolenoidModule(), forwardChannel, reverseChannel);
048  }
049
050  /**
051   * Constructor.
052   *
053   * @param moduleNumber   The module number of the solenoid module to use.
054   * @param forwardChannel The forward channel on the module to control (0..7).
055   * @param reverseChannel The reverse channel on the module to control (0..7).
056   */
057  public DoubleSolenoid(final int moduleNumber, final int forwardChannel,
058                        final int reverseChannel) {
059    super(moduleNumber);
060
061    checkSolenoidModule(m_moduleNumber);
062    checkSolenoidChannel(forwardChannel);
063    checkSolenoidChannel(reverseChannel);
064
065    int portHandle = SolenoidJNI.getPortWithModule((byte) m_moduleNumber, (byte) forwardChannel);
066    m_forwardHandle = SolenoidJNI.initializeSolenoidPort(portHandle);
067
068    try {
069      portHandle = SolenoidJNI.getPortWithModule((byte) m_moduleNumber, (byte) reverseChannel);
070      m_reverseHandle = SolenoidJNI.initializeSolenoidPort(portHandle);
071    } catch (RuntimeException ex) {
072      // free the forward handle on exception, then rethrow
073      SolenoidJNI.freeSolenoidPort(m_forwardHandle);
074      m_forwardHandle = 0;
075      m_reverseHandle = 0;
076      throw ex;
077    }
078
079    m_forwardMask = (byte) (1 << forwardChannel);
080    m_reverseMask = (byte) (1 << reverseChannel);
081
082    HAL.report(tResourceType.kResourceType_Solenoid, forwardChannel,
083                                   m_moduleNumber);
084    HAL.report(tResourceType.kResourceType_Solenoid, reverseChannel,
085                                   m_moduleNumber);
086    LiveWindow.addActuator("DoubleSolenoid", m_moduleNumber, forwardChannel, this);
087  }
088
089  /**
090   * Destructor.
091   */
092  public synchronized void free() {
093    SolenoidJNI.freeSolenoidPort(m_forwardHandle);
094    SolenoidJNI.freeSolenoidPort(m_reverseHandle);
095    super.free();
096  }
097
098  /**
099   * Set the value of a solenoid.
100   *
101   * @param value The value to set (Off, Forward, Reverse)
102   */
103  public void set(final Value value) {
104    boolean forward = false;
105    boolean reverse = false;
106
107    switch (value) {
108      case kOff:
109        forward = false;
110        reverse = false;
111        break;
112      case kForward:
113        forward = true;
114        reverse = false;
115        break;
116      case kReverse:
117        forward = false;
118        reverse = true;
119        break;
120      default:
121        throw new AssertionError("Illegal value: " + value);
122
123    }
124
125    SolenoidJNI.setSolenoid(m_forwardHandle, forward);
126    SolenoidJNI.setSolenoid(m_reverseHandle, reverse);
127  }
128
129  /**
130   * Read the current value of the solenoid.
131   *
132   * @return The current value of the solenoid.
133   */
134  public Value get() {
135    boolean valueForward = SolenoidJNI.getSolenoid(m_forwardHandle);
136    boolean valueReverse = SolenoidJNI.getSolenoid(m_reverseHandle);
137
138    if (valueForward) {
139      return Value.kForward;
140    }
141    if (valueReverse) {
142      return Value.kReverse;
143    }
144    return Value.kOff;
145  }
146
147  /**
148   * Check if the forward solenoid is blacklisted. If a solenoid is shorted, it is added to the
149   * blacklist and disabled until power cycle, or until faults are cleared.
150   *
151   * @return If solenoid is disabled due to short.
152   * @see #clearAllPCMStickyFaults()
153   */
154  public boolean isFwdSolenoidBlackListed() {
155    int blackList = getPCMSolenoidBlackList();
156    return (blackList & m_forwardMask) != 0;
157  }
158
159  /**
160   * Check if the reverse solenoid is blacklisted. If a solenoid is shorted, it is added to the
161   * blacklist and disabled until power cycle, or until faults are cleared.
162   *
163   * @return If solenoid is disabled due to short.
164   * @see #clearAllPCMStickyFaults()
165   */
166  public boolean isRevSolenoidBlackListed() {
167    int blackList = getPCMSolenoidBlackList();
168    return (blackList & m_reverseMask) != 0;
169  }
170
171  /*
172   * Live Window code, only does anything if live window is activated.
173   */
174  public String getSmartDashboardType() {
175    return "Double Solenoid";
176  }
177
178  private ITable m_table;
179  private ITableListener m_tableListener;
180
181  @Override
182  public void initTable(ITable subtable) {
183    m_table = subtable;
184    updateTable();
185  }
186
187  @Override
188  public ITable getTable() {
189    return m_table;
190  }
191
192  @Override
193  public void updateTable() {
194    if (m_table != null) {
195      m_table.putString("Value", get().name().substring(1));
196    }
197  }
198
199  @Override
200  public void startLiveWindowMode() {
201    set(Value.kOff); // Stop for safety
202    m_tableListener = (source, key, value, isNew) -> {
203      if ("Reverse".equals(value.toString())) {
204        set(Value.kReverse);
205      } else if ("Forward".equals(value.toString())) {
206        set(Value.kForward);
207      } else {
208        set(Value.kOff);
209      }
210    };
211    m_table.addTableListener("Value", m_tableListener, true);
212  }
213
214  @Override
215  public void stopLiveWindowMode() {
216    set(Value.kOff); // Stop for safety
217    // TODO: Broken, should only remove the listener from "Value" only.
218    m_table.removeTableListener(m_tableListener);
219  }
220}