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      }
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      }
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      }
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      }
225      default -> {
226        // Cannot hit this, limited by Value enum
227      }
228    }
229  }
230
231  /**
232   * Get the Relay State.
233   *
234   * <p>Gets the current state of the relay.
235   *
236   * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not
237   * kForward/kReverse (per the recommendation in Set)
238   *
239   * @return The current state of the relay as a Relay::Value
240   */
241  public Value get() {
242    if (m_direction == Direction.kForward) {
243      if (RelayJNI.getRelay(m_forwardHandle)) {
244        return Value.kOn;
245      } else {
246        return Value.kOff;
247      }
248    } else if (m_direction == Direction.kReverse) {
249      if (RelayJNI.getRelay(m_reverseHandle)) {
250        return Value.kOn;
251      } else {
252        return Value.kOff;
253      }
254    } else {
255      if (RelayJNI.getRelay(m_forwardHandle)) {
256        if (RelayJNI.getRelay(m_reverseHandle)) {
257          return Value.kOn;
258        } else {
259          return Value.kForward;
260        }
261      } else {
262        if (RelayJNI.getRelay(m_reverseHandle)) {
263          return Value.kReverse;
264        } else {
265          return Value.kOff;
266        }
267      }
268    }
269  }
270
271  /**
272   * Get the channel number.
273   *
274   * @return The channel number.
275   */
276  public int getChannel() {
277    return m_channel;
278  }
279
280  @Override
281  public void stopMotor() {
282    set(Value.kOff);
283  }
284
285  @Override
286  public String getDescription() {
287    return "Relay ID " + getChannel();
288  }
289
290  /**
291   * Set the Relay Direction.
292   *
293   * <p>Changes which values the relay can be set to depending on which direction is used
294   *
295   * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly
296   *
297   * @param direction The direction for the relay to operate in
298   */
299  @SuppressWarnings("this-escape")
300  public void setDirection(Direction direction) {
301    requireNonNullParam(direction, "direction", "setDirection");
302    if (m_direction == direction) {
303      return;
304    }
305
306    freeRelay();
307    m_direction = direction;
308    initRelay();
309  }
310
311  @Override
312  public void initSendable(SendableBuilder builder) {
313    builder.setSmartDashboardType("Relay");
314    builder.setActuator(true);
315    builder.setSafeState(() -> set(Value.kOff));
316    builder.addStringProperty(
317        "Value",
318        () -> get().getPrettyValue(),
319        value -> set(Value.getValueOf(value).orElse(Value.kOff)));
320  }
321}