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.FRCNetComm.tResourceType;
008import edu.wpi.first.hal.HAL;
009import edu.wpi.first.hal.util.AllocationException;
010import edu.wpi.first.util.sendable.Sendable;
011import edu.wpi.first.util.sendable.SendableBuilder;
012import edu.wpi.first.util.sendable.SendableRegistry;
013
014/**
015 * DoubleSolenoid class for running 2 channels of high voltage Digital Output on the pneumatics
016 * module.
017 *
018 * <p>The DoubleSolenoid class is typically used for pneumatics solenoids that have two positions
019 * controlled by two separate channels.
020 */
021public class DoubleSolenoid implements Sendable, AutoCloseable {
022  /** Possible values for a DoubleSolenoid. */
023  public enum Value {
024    kOff,
025    kForward,
026    kReverse
027  }
028
029  private final int m_forwardMask; // The mask for the forward channel.
030  private final int m_reverseMask; // The mask for the reverse channel.
031  private final int m_mask; // The channel mask
032  private PneumaticsBase m_module;
033  private final int m_forwardChannel;
034  private final int m_reverseChannel;
035
036  /**
037   * Constructs a double solenoid for a default module of a specific module type.
038   *
039   * @param moduleType The module type to use.
040   * @param forwardChannel The forward channel on the module to control.
041   * @param reverseChannel The reverse channel on the module to control.
042   */
043  public DoubleSolenoid(
044      final PneumaticsModuleType moduleType, final int forwardChannel, final int reverseChannel) {
045    this(PneumaticsBase.getDefaultForType(moduleType), moduleType, forwardChannel, reverseChannel);
046  }
047
048  /**
049   * Constructs a double solenoid for a specified module of a specific module type.
050   *
051   * @param module The module of the solenoid module to use.
052   * @param moduleType The module type to use.
053   * @param forwardChannel The forward channel on the module to control.
054   * @param reverseChannel The reverse channel on the module to control.
055   */
056  @SuppressWarnings("PMD.UseTryWithResources")
057  public DoubleSolenoid(
058      final int module,
059      final PneumaticsModuleType moduleType,
060      final int forwardChannel,
061      final int reverseChannel) {
062    m_module = PneumaticsBase.getForType(module, moduleType);
063    boolean allocatedSolenoids = false;
064    boolean successfulCompletion = false;
065
066    m_forwardChannel = forwardChannel;
067    m_reverseChannel = reverseChannel;
068
069    m_forwardMask = 1 << forwardChannel;
070    m_reverseMask = 1 << reverseChannel;
071    m_mask = m_forwardMask | m_reverseMask;
072
073    try {
074      if (!m_module.checkSolenoidChannel(forwardChannel)) {
075        throw new IllegalArgumentException("Channel " + forwardChannel + " out of range");
076      }
077
078      if (!m_module.checkSolenoidChannel(reverseChannel)) {
079        throw new IllegalArgumentException("Channel " + reverseChannel + " out of range");
080      }
081
082      int allocMask = m_module.checkAndReserveSolenoids(m_mask);
083      if (allocMask != 0) {
084        if (allocMask == m_mask) {
085          throw new AllocationException(
086              "Channels " + forwardChannel + " and " + reverseChannel + " already allocated");
087        } else if (allocMask == m_forwardMask) {
088          throw new AllocationException("Channel " + forwardChannel + " already allocated");
089        } else {
090          throw new AllocationException("Channel " + reverseChannel + " already allocated");
091        }
092      }
093      allocatedSolenoids = true;
094
095      HAL.report(
096          tResourceType.kResourceType_Solenoid, forwardChannel + 1, m_module.getModuleNumber() + 1);
097      HAL.report(
098          tResourceType.kResourceType_Solenoid, reverseChannel + 1, m_module.getModuleNumber() + 1);
099      SendableRegistry.addLW(this, "DoubleSolenoid", m_module.getModuleNumber(), forwardChannel);
100      successfulCompletion = true;
101    } finally {
102      if (!successfulCompletion) {
103        if (allocatedSolenoids) {
104          m_module.unreserveSolenoids(m_mask);
105        }
106        m_module.close();
107      }
108    }
109  }
110
111  @Override
112  public synchronized void close() {
113    SendableRegistry.remove(this);
114    m_module.unreserveSolenoids(m_mask);
115    m_module.close();
116    m_module = null;
117  }
118
119  /**
120   * Set the value of a solenoid.
121   *
122   * @param value The value to set (Off, Forward, Reverse)
123   */
124  public void set(final Value value) {
125    int setValue;
126
127    switch (value) {
128      case kOff:
129        setValue = 0;
130        break;
131      case kForward:
132        setValue = m_forwardMask;
133        break;
134      case kReverse:
135        setValue = m_reverseMask;
136        break;
137      default:
138        throw new AssertionError("Illegal value: " + value);
139    }
140
141    m_module.setSolenoids(m_mask, setValue);
142  }
143
144  /**
145   * Read the current value of the solenoid.
146   *
147   * @return The current value of the solenoid.
148   */
149  public Value get() {
150    int values = m_module.getSolenoids();
151
152    if ((values & m_forwardMask) != 0) {
153      return Value.kForward;
154    } else if ((values & m_reverseMask) != 0) {
155      return Value.kReverse;
156    } else {
157      return Value.kOff;
158    }
159  }
160
161  /**
162   * Toggle the value of the solenoid.
163   *
164   * <p>If the solenoid is set to forward, it'll be set to reverse. If the solenoid is set to
165   * reverse, it'll be set to forward. If the solenoid is set to off, nothing happens.
166   */
167  public void toggle() {
168    Value value = get();
169
170    if (value == Value.kForward) {
171      set(Value.kReverse);
172    } else if (value == Value.kReverse) {
173      set(Value.kForward);
174    }
175  }
176
177  /**
178   * Get the forward channel.
179   *
180   * @return the forward channel.
181   */
182  public int getFwdChannel() {
183    return m_forwardChannel;
184  }
185
186  /**
187   * Get the reverse channel.
188   *
189   * @return the reverse channel.
190   */
191  public int getRevChannel() {
192    return m_reverseChannel;
193  }
194
195  /**
196   * Check if the forward solenoid is Disabled. If a solenoid is shorted, it is added to the
197   * DisabledList and disabled until power cycle, or until faults are cleared.
198   *
199   * @return If solenoid is disabled due to short.
200   */
201  public boolean isFwdSolenoidDisabled() {
202    return (m_module.getSolenoidDisabledList() & m_forwardMask) != 0;
203  }
204
205  /**
206   * Check if the reverse solenoid is Disabled. If a solenoid is shorted, it is added to the
207   * DisabledList and disabled until power cycle, or until faults are cleared.
208   *
209   * @return If solenoid is disabled due to short.
210   */
211  public boolean isRevSolenoidDisabled() {
212    return (m_module.getSolenoidDisabledList() & m_reverseMask) != 0;
213  }
214
215  @Override
216  public void initSendable(SendableBuilder builder) {
217    builder.setSmartDashboardType("Double Solenoid");
218    builder.setActuator(true);
219    builder.setSafeState(() -> set(Value.kOff));
220    builder.addStringProperty(
221        "Value",
222        () -> get().name().substring(1),
223        value -> {
224          if ("Forward".equals(value)) {
225            set(Value.kForward);
226          } else if ("Reverse".equals(value)) {
227            set(Value.kReverse);
228          } else {
229            set(Value.kOff);
230          }
231        });
232  }
233}