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 static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.hal.FRCNetComm.tResourceType;
010import edu.wpi.first.hal.HAL;
011import edu.wpi.first.hal.RelayJNI;
012import edu.wpi.first.hal.util.HalHandleException;
013import edu.wpi.first.hal.util.UncleanStatusException;
014import edu.wpi.first.util.sendable.Sendable;
015import edu.wpi.first.util.sendable.SendableBuilder;
016import edu.wpi.first.util.sendable.SendableRegistry;
017import java.util.Arrays;
018import java.util.Optional;
019
020/**
021 * Class for VEX Robotics Spike style relay outputs. Relays are intended to be connected to Spikes
022 * or similar relays. The relay channels controls a pair of channels that are either both off, one
023 * on, the other on, or both on. This translates into two Spike outputs at 0v, one at 12v and one at
024 * 0v, one at 0v and the other at 12v, or two Spike outputs at 12V. This allows off, full forward,
025 * or full reverse control of motors without variable speed. It also allows the two channels
026 * (forward and reverse) to be used independently for something that does not care about voltage
027 * polarity (like a solenoid).
028 */
029public class Relay extends MotorSafety implements Sendable, AutoCloseable {
030  /**
031   * This class represents errors in trying to set relay values contradictory to the direction to
032   * which the relay is set.
033   */
034  @SuppressWarnings("serial")
035  public static class InvalidValueException extends RuntimeException {
036    /**
037     * Create a new exception with the given message.
038     *
039     * @param message the message to pass with the exception
040     */
041    public InvalidValueException(String message) {
042      super(message);
043    }
044  }
045
046  /** The state to drive a Relay to. */
047  public enum Value {
048    kOff("Off"),
049    kOn("On"),
050    kForward("Forward"),
051    kReverse("Reverse");
052
053    private final String m_prettyValue;
054
055    Value(String prettyValue) {
056      m_prettyValue = prettyValue;
057    }
058
059    public String getPrettyValue() {
060      return m_prettyValue;
061    }
062
063    public static Optional<Value> getValueOf(String value) {
064      return Arrays.stream(Value.values()).filter(v -> v.m_prettyValue.equals(value)).findFirst();
065    }
066  }
067
068  /** The Direction(s) that a relay is configured to operate in. */
069  public enum Direction {
070    /** direction: both directions are valid. */
071    kBoth,
072    /** direction: Only forward is valid. */
073    kForward,
074    /** direction: only reverse is valid. */
075    kReverse
076  }
077
078  private final int m_channel;
079
080  private int m_forwardHandle;
081  private int m_reverseHandle;
082
083  private Direction m_direction;
084
085  /**
086   * Common relay initialization method. This code is common to all Relay constructors and
087   * initializes the relay and reserves all resources that need to be locked. Initially the relay is
088   * set to both lines at 0v.
089   */
090  private void initRelay() {
091    SensorUtil.checkRelayChannel(m_channel);
092
093    int portHandle = HAL.getPort((byte) m_channel);
094    if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
095      m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true);
096      HAL.report(tResourceType.kResourceType_Relay, m_channel + 1);
097    }
098    if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
099      m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false);
100      HAL.report(tResourceType.kResourceType_Relay, m_channel + 128);
101    }
102
103    setSafetyEnabled(false);
104
105    SendableRegistry.addLW(this, "Relay", m_channel);
106  }
107
108  /**
109   * Relay constructor given a channel.
110   *
111   * @param channel The channel number for this relay (0 - 3).
112   * @param direction The direction that the Relay object will control.
113   */
114  public Relay(final int channel, Direction direction) {
115    m_channel = channel;
116    m_direction = requireNonNullParam(direction, "direction", "Relay");
117    initRelay();
118    set(Value.kOff);
119  }
120
121  /**
122   * Relay constructor given a channel, allowing both directions.
123   *
124   * @param channel The channel number for this relay (0 - 3).
125   */
126  public Relay(final int channel) {
127    this(channel, Direction.kBoth);
128  }
129
130  @Override
131  public void close() {
132    SendableRegistry.remove(this);
133    freeRelay();
134  }
135
136  private void freeRelay() {
137    try {
138      RelayJNI.setRelay(m_forwardHandle, false);
139    } catch (UncleanStatusException | HalHandleException ignored) {
140      // do nothing. Ignore
141    }
142    try {
143      RelayJNI.setRelay(m_reverseHandle, false);
144    } catch (UncleanStatusException | HalHandleException ignored) {
145      // do nothing. Ignore
146    }
147
148    RelayJNI.freeRelayPort(m_forwardHandle);
149    RelayJNI.freeRelayPort(m_reverseHandle);
150
151    m_forwardHandle = 0;
152    m_reverseHandle = 0;
153  }
154
155  /**
156   * Set the relay state.
157   *
158   * <p>Valid values depend on which directions of the relay are controlled by the object.
159   *
160   * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v,
161   * 0v-12v, 12v-12v
162   *
163   * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction or
164   * you can simply specify kOff and kOn. Using only kOff and kOn is recommended.
165   *
166   * @param value The state to set the relay.
167   */
168  public void set(Value value) {
169    switch (value) {
170      case kOff:
171        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
172          RelayJNI.setRelay(m_forwardHandle, false);
173        }
174        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
175          RelayJNI.setRelay(m_reverseHandle, false);
176        }
177        break;
178      case kOn:
179        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
180          RelayJNI.setRelay(m_forwardHandle, true);
181        }
182        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
183          RelayJNI.setRelay(m_reverseHandle, true);
184        }
185        break;
186      case kForward:
187        if (m_direction == Direction.kReverse) {
188          throw new InvalidValueException(
189              "A relay configured for reverse cannot be set to " + "forward");
190        }
191        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
192          RelayJNI.setRelay(m_forwardHandle, true);
193        }
194        if (m_direction == Direction.kBoth) {
195          RelayJNI.setRelay(m_reverseHandle, false);
196        }
197        break;
198      case kReverse:
199        if (m_direction == Direction.kForward) {
200          throw new InvalidValueException(
201              "A relay configured for forward cannot be set to " + "reverse");
202        }
203        if (m_direction == Direction.kBoth) {
204          RelayJNI.setRelay(m_forwardHandle, false);
205        }
206        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
207          RelayJNI.setRelay(m_reverseHandle, true);
208        }
209        break;
210      default:
211        // Cannot hit this, limited by Value enum
212    }
213  }
214
215  /**
216   * Get the Relay State.
217   *
218   * <p>Gets the current state of the relay.
219   *
220   * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not
221   * kForward/kReverse (per the recommendation in Set)
222   *
223   * @return The current state of the relay as a Relay::Value
224   */
225  public Value get() {
226    if (m_direction == Direction.kForward) {
227      if (RelayJNI.getRelay(m_forwardHandle)) {
228        return Value.kOn;
229      } else {
230        return Value.kOff;
231      }
232    } else if (m_direction == Direction.kReverse) {
233      if (RelayJNI.getRelay(m_reverseHandle)) {
234        return Value.kOn;
235      } else {
236        return Value.kOff;
237      }
238    } else {
239      if (RelayJNI.getRelay(m_forwardHandle)) {
240        if (RelayJNI.getRelay(m_reverseHandle)) {
241          return Value.kOn;
242        } else {
243          return Value.kForward;
244        }
245      } else {
246        if (RelayJNI.getRelay(m_reverseHandle)) {
247          return Value.kReverse;
248        } else {
249          return Value.kOff;
250        }
251      }
252    }
253  }
254
255  /**
256   * Get the channel number.
257   *
258   * @return The channel number.
259   */
260  public int getChannel() {
261    return m_channel;
262  }
263
264  @Override
265  public void stopMotor() {
266    set(Value.kOff);
267  }
268
269  @Override
270  public String getDescription() {
271    return "Relay ID " + getChannel();
272  }
273
274  /**
275   * Set the Relay Direction.
276   *
277   * <p>Changes which values the relay can be set to depending on which direction is used
278   *
279   * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly
280   *
281   * @param direction The direction for the relay to operate in
282   */
283  public void setDirection(Direction direction) {
284    requireNonNullParam(direction, "direction", "setDirection");
285    if (m_direction == direction) {
286      return;
287    }
288
289    freeRelay();
290    m_direction = direction;
291    initRelay();
292  }
293
294  @Override
295  public void initSendable(SendableBuilder builder) {
296    builder.setSmartDashboardType("Relay");
297    builder.setActuator(true);
298    builder.setSafeState(() -> set(Value.kOff));
299    builder.addStringProperty(
300        "Value",
301        () -> get().getPrettyValue(),
302        value -> set(Value.getValueOf(value).orElse(Value.kOff)));
303  }
304}