001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-2017. 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.livewindow.LiveWindow;
014import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
015import edu.wpi.first.wpilibj.tables.ITable;
016import edu.wpi.first.wpilibj.tables.ITableListener;
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 SensorBase implements MotorSafety, LiveWindowSendable {
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    /**
039     * Create a new exception with the given message.
040     *
041     * @param message the message to pass with the exception
042     */
043    public InvalidValueException(String message) {
044      super(message);
045    }
046  }
047
048  /**
049   * The state to drive a Relay to.
050   */
051  public enum Value {
052    kOff, kOn, kForward, kReverse
053  }
054
055  /**
056   * The Direction(s) that a relay is configured to operate in.
057   */
058  public enum Direction {
059    /**
060     * direction: both directions are valid.
061     */
062
063    kBoth,
064    /**
065     * direction: Only forward is valid.
066     */
067    kForward,
068    /**
069     * direction: only reverse is valid.
070     */
071    kReverse
072  }
073
074  private final int m_channel;
075
076  private int m_forwardHandle = 0;
077  private int m_reverseHandle = 0;
078
079  private Direction m_direction;
080
081  /**
082   * Common relay initialization method. This code is common to all Relay constructors and
083   * initializes the relay and reserves all resources that need to be locked. Initially the relay is
084   * set to both lines at 0v.
085   */
086  private void initRelay() {
087    SensorBase.checkRelayChannel(m_channel);
088
089    int portHandle = RelayJNI.getPort((byte)m_channel);
090    if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
091      m_forwardHandle = RelayJNI.initializeRelayPort(portHandle, true);
092      HAL.report(tResourceType.kResourceType_Relay, m_channel);
093    }
094    if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
095      m_reverseHandle = RelayJNI.initializeRelayPort(portHandle, false);
096      HAL.report(tResourceType.kResourceType_Relay, m_channel + 128);
097    }
098
099    m_safetyHelper = new MotorSafetyHelper(this);
100    m_safetyHelper.setSafetyEnabled(false);
101
102    LiveWindow.addActuator("Relay", m_channel, this);
103  }
104
105  /**
106   * Relay constructor given a channel.
107   *
108   * @param channel   The channel number for this relay (0 - 3).
109   * @param direction The direction that the Relay object will control.
110   */
111  public Relay(final int channel, Direction direction) {
112    m_channel = channel;
113    m_direction = requireNonNull( direction, "Null Direction was given");
114    initRelay();
115    set(Value.kOff);
116  }
117
118  /**
119   * Relay constructor given a channel, allowing both directions.
120   *
121   * @param channel The channel number for this relay (0 - 3).
122   */
123  public Relay(final int channel) {
124    this(channel, Direction.kBoth);
125  }
126
127  @Override
128  public void free() {
129    try {
130      RelayJNI.setRelay(m_forwardHandle, false);
131    } catch (RuntimeException ex) {
132      // do nothing. Ignore
133    }
134    try {
135      RelayJNI.setRelay(m_reverseHandle, false);
136    } catch (RuntimeException ex) {
137      // do nothing. Ignore
138    }
139
140    RelayJNI.freeRelayPort(m_forwardHandle);
141    RelayJNI.freeRelayPort(m_reverseHandle);
142
143    m_forwardHandle = 0;
144    m_reverseHandle = 0;
145  }
146
147  /**
148   * Set the relay state.
149   *
150   * <p>Valid values depend on which directions of the relay are controlled by the object.
151   *
152   * <p>When set to kBothDirections, the relay can be set to any of the four states: 0v-0v, 12v-0v,
153   * 0v-12v, 12v-12v
154   *
155   * <p>When set to kForwardOnly or kReverseOnly, you can specify the constant for the direction or
156   * you can simply specify kOff_val and kOn_val. Using only kOff_val and kOn_val is recommended.
157   *
158   * @param value The state to set the relay.
159   */
160  public void set(Value value) {
161    switch (value) {
162      case kOff:
163        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
164          RelayJNI.setRelay(m_forwardHandle, false);
165        }
166        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
167          RelayJNI.setRelay(m_reverseHandle, false);
168        }
169        break;
170      case kOn:
171        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
172          RelayJNI.setRelay(m_forwardHandle, true);
173        }
174        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
175          RelayJNI.setRelay(m_reverseHandle, true);
176        }
177        break;
178      case kForward:
179        if (m_direction == Direction.kReverse) {
180          throw new InvalidValueException("A relay configured for reverse cannot be set to "
181              + "forward");
182        }
183        if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
184          RelayJNI.setRelay(m_forwardHandle, true);
185        }
186        if (m_direction == Direction.kBoth) {
187          RelayJNI.setRelay(m_reverseHandle, false);
188        }
189        break;
190      case kReverse:
191        if (m_direction == Direction.kForward) {
192          throw new InvalidValueException("A relay configured for forward cannot be set to "
193              + "reverse");
194        }
195        if (m_direction == Direction.kBoth) {
196          RelayJNI.setRelay(m_forwardHandle, false);
197        }
198        if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
199          RelayJNI.setRelay(m_reverseHandle, true);
200        }
201        break;
202      default:
203        // Cannot hit this, limited by Value enum
204    }
205  }
206
207  /**
208   * Get the Relay State.
209   *
210   * <p>Gets the current state of the relay.
211   *
212   * <p>When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff not
213   * kForward/kReverse (per the recommendation in Set)
214   *
215   * @return The current state of the relay as a Relay::Value
216   */
217  public Value get() {
218    if (RelayJNI.getRelay(m_forwardHandle)) {
219      if (RelayJNI.getRelay(m_reverseHandle)) {
220        return Value.kOn;
221      } else {
222        if (m_direction == Direction.kForward) {
223          return Value.kOn;
224        } else {
225          return Value.kForward;
226        }
227      }
228    } else {
229      if (RelayJNI.getRelay(m_reverseHandle)) {
230        if (m_direction == Direction.kReverse) {
231          return Value.kOn;
232        } else {
233          return Value.kReverse;
234        }
235      } else {
236        return Value.kOff;
237      }
238    }
239  }
240
241  /**
242   * Get the channel number.
243   *
244   * @return The channel number.
245   */
246  public int getChannel() {
247    return m_channel;
248  }
249
250  @Override
251  public void setExpiration(double timeout) {
252    m_safetyHelper.setExpiration(timeout);
253  }
254
255  @Override
256  public double getExpiration() {
257    return m_safetyHelper.getExpiration();
258  }
259
260  @Override
261  public boolean isAlive() {
262    return m_safetyHelper.isAlive();
263  }
264
265  @Override
266  public void stopMotor() {
267    set(Value.kOff);
268  }
269
270  @Override
271  public boolean isSafetyEnabled() {
272    return m_safetyHelper.isSafetyEnabled();
273  }
274
275  @Override
276  public void setSafetyEnabled(boolean enabled) {
277    m_safetyHelper.setSafetyEnabled(enabled);
278  }
279
280  @Override
281  public String getDescription() {
282    return "Relay ID " + getChannel();
283  }
284
285  /**
286   * Set the Relay Direction.
287   *
288   * <p>Changes which values the relay can be set to depending on which direction is used
289   *
290   * <p>Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly
291   *
292   * @param direction The direction for the relay to operate in
293   */
294  public void setDirection(Direction direction) {
295    if (direction == null) {
296      throw new NullPointerException("Null Direction was given");
297    }
298    if (m_direction == direction) {
299      return;
300    }
301
302    free();
303
304    m_direction = direction;
305
306    initRelay();
307  }
308
309  /*
310   * Live Window code, only does anything if live window is activated.
311   */
312  @Override
313  public String getSmartDashboardType() {
314    return "Relay";
315  }
316
317  private ITable m_table;
318  private ITableListener m_tableListener;
319
320  @Override
321  public void initTable(ITable subtable) {
322    m_table = subtable;
323    updateTable();
324  }
325
326  @Override
327  public ITable getTable() {
328    return m_table;
329  }
330
331  @Override
332  public void updateTable() {
333    if (m_table != null) {
334      if (get() == Value.kOn) {
335        m_table.putString("Value", "On");
336      } else if (get() == Value.kForward) {
337        m_table.putString("Value", "Forward");
338      } else if (get() == Value.kReverse) {
339        m_table.putString("Value", "Reverse");
340      } else {
341        m_table.putString("Value", "Off");
342      }
343    }
344  }
345
346  @Override
347  public void startLiveWindowMode() {
348    m_tableListener = new ITableListener() {
349      @Override
350      public void valueChanged(ITable itable, String key, Object value, boolean bln) {
351        String val = ((String) value);
352        if (val.equals("Off")) {
353          set(Value.kOff);
354        } else if (val.equals("On")) {
355          set(Value.kOn);
356        } else if (val.equals("Forward")) {
357          set(Value.kForward);
358        } else if (val.equals("Reverse")) {
359          set(Value.kReverse);
360        }
361      }
362    };
363    m_table.addTableListener("Value", m_tableListener, true);
364  }
365
366  @Override
367  public void stopLiveWindowMode() {
368    // TODO: Broken, should only remove the listener from "Value" only.
369    m_table.removeTableListener(m_tableListener);
370  }
371}