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.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  public static class InvalidValueException extends RuntimeException {
035    /**
036     * Create a new exception with the given message.
037     *
038     * @param message the message to pass with the exception
039     */
040    public InvalidValueException(String message) {
041      super(message);
042    }
043  }
044
045  /** The state to drive a Relay to. */
046  public enum Value {
047    kOff("Off"),
048    kOn("On"),
049    kForward("Forward"),
050    kReverse("Reverse");
051
052    private final String m_prettyValue;
053
054    Value(String prettyValue) {
055      m_prettyValue = prettyValue;
056    }
057
058    public String getPrettyValue() {
059      return m_prettyValue;
060    }
061
062    public static Optional<Value> getValueOf(String value) {
063      return Arrays.stream(Value.values()).filter(v -> v.m_prettyValue.equals(value)).findFirst();
064    }
065  }
066
067  /** The Direction(s) that a relay is configured to operate in. */
068  public enum Direction {
069    /** direction: both directions are valid. */
070    kBoth,
071    /** direction: Only forward is valid. */
072    kForward,
073    /** direction: only reverse is valid. */
074    kReverse
075  }
076
077  private final int m_channel;
078
079  private int m_forwardHandle;
080  private int m_reverseHandle;
081
082  private Direction m_direction;
083
084  /**
085   * Common relay initialization method. This code is common to all Relay constructors and
086   * initializes the relay and reserves all resources that need to be locked. Initially the relay is
087   * set to both lines at 0v.
088   */
089  private void initRelay() {
090    SensorUtil.checkRelayChannel(m_channel);
091
092    int portHandle = HAL.getPort((byte) m_channel);
093    if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
094      m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true);
095      HAL.report(tResourceType.kResourceType_Relay, m_channel + 1);
096    }
097    if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
098      m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false);
099      HAL.report(tResourceType.kResourceType_Relay, m_channel + 128);
100    }
101
102    setSafetyEnabled(false);
103
104    SendableRegistry.addLW(this, "Relay", m_channel);
105  }
106
107  /**
108   * Relay constructor given a channel.
109   *
110   * @param channel The channel number for this relay (0 - 3).
111   * @param direction The direction that the Relay object will control.
112   */
113  @SuppressWarnings("this-escape")
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  @SuppressWarnings("this-escape")
284  public void setDirection(Direction direction) {
285    requireNonNullParam(direction, "direction", "setDirection");
286    if (m_direction == direction) {
287      return;
288    }
289
290    freeRelay();
291    m_direction = direction;
292    initRelay();
293  }
294
295  @Override
296  public void initSendable(SendableBuilder builder) {
297    builder.setSmartDashboardType("Relay");
298    builder.setActuator(true);
299    builder.setSafeState(() -> set(Value.kOff));
300    builder.addStringProperty(
301        "Value",
302        () -> get().getPrettyValue(),
303        value -> set(Value.getValueOf(value).orElse(Value.kOff)));
304  }
305}