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    /** Off. */
048    kOff("Off"),
049    /** On. */
050    kOn("On"),
051    /** Forward. */
052    kForward("Forward"),
053    /** Reverse. */
054    kReverse("Reverse");
055
056    private final String m_prettyValue;
057
058    Value(String prettyValue) {
059      m_prettyValue = prettyValue;
060    }
061
062    /**
063     * Returns the pretty string representation of the value.
064     *
065     * @return The pretty string representation of the value.
066     */
067    public String getPrettyValue() {
068      return m_prettyValue;
069    }
070
071    /**
072     * Returns the value for a given pretty string.
073     *
074     * @param value The pretty string.
075     * @return The value or an empty optional if there is no corresponding value.
076     */
077    public static Optional<Value> getValueOf(String value) {
078      return Arrays.stream(Value.values()).filter(v -> v.m_prettyValue.equals(value)).findFirst();
079    }
080  }
081
082  /** The Direction(s) that a relay is configured to operate in. */
083  public enum Direction {
084    /** Both directions are valid. */
085    kBoth,
086    /** Only forward is valid. */
087    kForward,
088    /** Only reverse is valid. */
089    kReverse
090  }
091
092  private final int m_channel;
093
094  private int m_forwardHandle;
095  private int m_reverseHandle;
096
097  private Direction m_direction;
098
099  /**
100   * Common relay initialization method. This code is common to all Relay constructors and
101   * initializes the relay and reserves all resources that need to be locked. Initially the relay is
102   * set to both lines at 0v.
103   */
104  private void initRelay() {
105    SensorUtil.checkRelayChannel(m_channel);
106
107    int portHandle = HAL.getPort((byte) m_channel);
108    if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
109      m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true);
110      HAL.report(tResourceType.kResourceType_Relay, m_channel + 1);
111    }
112    if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
113      m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false);
114      HAL.report(tResourceType.kResourceType_Relay, m_channel + 128);
115    }
116
117    setSafetyEnabled(false);
118
119    SendableRegistry.addLW(this, "Relay", m_channel);
120  }
121
122  /**
123   * Relay constructor given a channel.
124   *
125   * @param channel The channel number for this relay (0 - 3).
126   * @param direction The direction that the Relay object will control.
127   */
128  @SuppressWarnings("this-escape")
129  public Relay(final int channel, Direction direction) {
130    m_channel = channel;
131    m_direction = requireNonNullParam(direction, "direction", "Relay");
132    initRelay();
133    set(Value.kOff);
134  }
135
136  /**
137   * Relay constructor given a channel, allowing both directions.
138   *
139   * @param channel The channel number for this relay (0 - 3).
140   */
141  public Relay(final int channel) {
142    this(channel, Direction.kBoth);
143  }
144
145  @Override
146  public void close() {
147    SendableRegistry.remove(this);
148    freeRelay();
149  }
150
151  private void freeRelay() {
152    try {
153      RelayJNI.setRelay(m_forwardHandle, false);
154    } catch (UncleanStatusException | HalHandleException ignored) {
155      // do nothing. Ignore
156    }
157    try {
158      RelayJNI.setRelay(m_reverseHandle, false);
159    } catch (UncleanStatusException | HalHandleException ignored) {
160      // do nothing. Ignore
161    }
162
163    RelayJNI.freeRelayPort(m_forwardHandle);
164    RelayJNI.freeRelayPort(m_reverseHandle);
165
166    m_forwardHandle = 0;
167    m_reverseHandle = 0;
168  }
169
170  /**
171   * Set the relay state.
172   *
173   * <p>Valid values depend on which directions of the relay are controlled by the object.
174   *
175   * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v,
176   * 0v-12v, 12v-12v
177   *
178   * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction, or
179   * you can simply specify kOff and kOn. Using only kOff and kOn is recommended.
180   *
181   * @param value The state to set the relay.
182   */
183  public void set(Value value) {
184    switch (value) {
185      case kOff:
186        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
187          RelayJNI.setRelay(m_forwardHandle, false);
188        }
189        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
190          RelayJNI.setRelay(m_reverseHandle, false);
191        }
192        break;
193      case kOn:
194        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
195          RelayJNI.setRelay(m_forwardHandle, true);
196        }
197        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
198          RelayJNI.setRelay(m_reverseHandle, true);
199        }
200        break;
201      case kForward:
202        if (m_direction == Direction.kReverse) {
203          throw new InvalidValueException(
204              "A relay configured for reverse cannot be set to " + "forward");
205        }
206        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
207          RelayJNI.setRelay(m_forwardHandle, true);
208        }
209        if (m_direction == Direction.kBoth) {
210          RelayJNI.setRelay(m_reverseHandle, false);
211        }
212        break;
213      case kReverse:
214        if (m_direction == Direction.kForward) {
215          throw new InvalidValueException(
216              "A relay configured for forward cannot be set to " + "reverse");
217        }
218        if (m_direction == Direction.kBoth) {
219          RelayJNI.setRelay(m_forwardHandle, false);
220        }
221        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
222          RelayJNI.setRelay(m_reverseHandle, true);
223        }
224        break;
225      default:
226        // Cannot hit this, limited by Value enum
227    }
228  }
229
230  /**
231   * Get the Relay State.
232   *
233   * <p>Gets the current state of the relay.
234   *
235   * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not
236   * kForward/kReverse (per the recommendation in Set)
237   *
238   * @return The current state of the relay as a Relay::Value
239   */
240  public Value get() {
241    if (m_direction == Direction.kForward) {
242      if (RelayJNI.getRelay(m_forwardHandle)) {
243        return Value.kOn;
244      } else {
245        return Value.kOff;
246      }
247    } else if (m_direction == Direction.kReverse) {
248      if (RelayJNI.getRelay(m_reverseHandle)) {
249        return Value.kOn;
250      } else {
251        return Value.kOff;
252      }
253    } else {
254      if (RelayJNI.getRelay(m_forwardHandle)) {
255        if (RelayJNI.getRelay(m_reverseHandle)) {
256          return Value.kOn;
257        } else {
258          return Value.kForward;
259        }
260      } else {
261        if (RelayJNI.getRelay(m_reverseHandle)) {
262          return Value.kReverse;
263        } else {
264          return Value.kOff;
265        }
266      }
267    }
268  }
269
270  /**
271   * Get the channel number.
272   *
273   * @return The channel number.
274   */
275  public int getChannel() {
276    return m_channel;
277  }
278
279  @Override
280  public void stopMotor() {
281    set(Value.kOff);
282  }
283
284  @Override
285  public String getDescription() {
286    return "Relay ID " + getChannel();
287  }
288
289  /**
290   * Set the Relay Direction.
291   *
292   * <p>Changes which values the relay can be set to depending on which direction is used
293   *
294   * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly
295   *
296   * @param direction The direction for the relay to operate in
297   */
298  @SuppressWarnings("this-escape")
299  public void setDirection(Direction direction) {
300    requireNonNullParam(direction, "direction", "setDirection");
301    if (m_direction == direction) {
302      return;
303    }
304
305    freeRelay();
306    m_direction = direction;
307    initRelay();
308  }
309
310  @Override
311  public void initSendable(SendableBuilder builder) {
312    builder.setSmartDashboardType("Relay");
313    builder.setActuator(true);
314    builder.setSafeState(() -> set(Value.kOff));
315    builder.addStringProperty(
316        "Value",
317        () -> get().getPrettyValue(),
318        value -> set(Value.getValueOf(value).orElse(Value.kOff)));
319  }
320}