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  public Relay(final int channel, Direction direction) {
114    m_channel = channel;
115    m_direction = requireNonNullParam(direction, "direction", "Relay");
116    initRelay();
117    set(Value.kOff);
118  }
119
120  /**
121   * Relay constructor given a channel, allowing both directions.
122   *
123   * @param channel The channel number for this relay (0 - 3).
124   */
125  public Relay(final int channel) {
126    this(channel, Direction.kBoth);
127  }
128
129  @Override
130  public void close() {
131    SendableRegistry.remove(this);
132    freeRelay();
133  }
134
135  private void freeRelay() {
136    try {
137      RelayJNI.setRelay(m_forwardHandle, false);
138    } catch (UncleanStatusException | HalHandleException ignored) {
139      // do nothing. Ignore
140    }
141    try {
142      RelayJNI.setRelay(m_reverseHandle, false);
143    } catch (UncleanStatusException | HalHandleException ignored) {
144      // do nothing. Ignore
145    }
146
147    RelayJNI.freeRelayPort(m_forwardHandle);
148    RelayJNI.freeRelayPort(m_reverseHandle);
149
150    m_forwardHandle = 0;
151    m_reverseHandle = 0;
152  }
153
154  /**
155   * Set the relay state.
156   *
157   * <p>Valid values depend on which directions of the relay are controlled by the object.
158   *
159   * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v,
160   * 0v-12v, 12v-12v
161   *
162   * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction, or
163   * you can simply specify kOff and kOn. Using only kOff and kOn is recommended.
164   *
165   * @param value The state to set the relay.
166   */
167  public void set(Value value) {
168    switch (value) {
169      case kOff:
170        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
171          RelayJNI.setRelay(m_forwardHandle, false);
172        }
173        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
174          RelayJNI.setRelay(m_reverseHandle, false);
175        }
176        break;
177      case kOn:
178        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
179          RelayJNI.setRelay(m_forwardHandle, true);
180        }
181        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
182          RelayJNI.setRelay(m_reverseHandle, true);
183        }
184        break;
185      case kForward:
186        if (m_direction == Direction.kReverse) {
187          throw new InvalidValueException(
188              "A relay configured for reverse cannot be set to " + "forward");
189        }
190        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
191          RelayJNI.setRelay(m_forwardHandle, true);
192        }
193        if (m_direction == Direction.kBoth) {
194          RelayJNI.setRelay(m_reverseHandle, false);
195        }
196        break;
197      case kReverse:
198        if (m_direction == Direction.kForward) {
199          throw new InvalidValueException(
200              "A relay configured for forward cannot be set to " + "reverse");
201        }
202        if (m_direction == Direction.kBoth) {
203          RelayJNI.setRelay(m_forwardHandle, false);
204        }
205        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
206          RelayJNI.setRelay(m_reverseHandle, true);
207        }
208        break;
209      default:
210        // Cannot hit this, limited by Value enum
211    }
212  }
213
214  /**
215   * Get the Relay State.
216   *
217   * <p>Gets the current state of the relay.
218   *
219   * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not
220   * kForward/kReverse (per the recommendation in Set)
221   *
222   * @return The current state of the relay as a Relay::Value
223   */
224  public Value get() {
225    if (m_direction == Direction.kForward) {
226      if (RelayJNI.getRelay(m_forwardHandle)) {
227        return Value.kOn;
228      } else {
229        return Value.kOff;
230      }
231    } else if (m_direction == Direction.kReverse) {
232      if (RelayJNI.getRelay(m_reverseHandle)) {
233        return Value.kOn;
234      } else {
235        return Value.kOff;
236      }
237    } else {
238      if (RelayJNI.getRelay(m_forwardHandle)) {
239        if (RelayJNI.getRelay(m_reverseHandle)) {
240          return Value.kOn;
241        } else {
242          return Value.kForward;
243        }
244      } else {
245        if (RelayJNI.getRelay(m_reverseHandle)) {
246          return Value.kReverse;
247        } else {
248          return Value.kOff;
249        }
250      }
251    }
252  }
253
254  /**
255   * Get the channel number.
256   *
257   * @return The channel number.
258   */
259  public int getChannel() {
260    return m_channel;
261  }
262
263  @Override
264  public void stopMotor() {
265    set(Value.kOff);
266  }
267
268  @Override
269  public String getDescription() {
270    return "Relay ID " + getChannel();
271  }
272
273  /**
274   * Set the Relay Direction.
275   *
276   * <p>Changes which values the relay can be set to depending on which direction is used
277   *
278   * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly
279   *
280   * @param direction The direction for the relay to operate in
281   */
282  public void setDirection(Direction direction) {
283    requireNonNullParam(direction, "direction", "setDirection");
284    if (m_direction == direction) {
285      return;
286    }
287
288    freeRelay();
289    m_direction = direction;
290    initRelay();
291  }
292
293  @Override
294  public void initSendable(SendableBuilder builder) {
295    builder.setSmartDashboardType("Relay");
296    builder.setActuator(true);
297    builder.setSafeState(() -> set(Value.kOff));
298    builder.addStringProperty(
299        "Value",
300        () -> get().getPrettyValue(),
301        value -> set(Value.getValueOf(value).orElse(Value.kOff)));
302  }
303}