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