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