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