001/*----------------------------------------------------------------------------*/
002/* Copyright (c) FIRST 2008-2012. 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 java.nio.ByteBuffer;
011import java.nio.ByteOrder;
012
013import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType;
014import edu.wpi.first.wpilibj.communication.UsageReporting;
015import edu.wpi.first.wpilibj.hal.DIOJNI;
016import edu.wpi.first.wpilibj.hal.HALUtil;
017import edu.wpi.first.wpilibj.hal.RelayJNI;
018import edu.wpi.first.wpilibj.livewindow.LiveWindow;
019import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
020import edu.wpi.first.wpilibj.tables.ITable;
021import edu.wpi.first.wpilibj.tables.ITableListener;
022import edu.wpi.first.wpilibj.util.AllocationException;
023import edu.wpi.first.wpilibj.util.CheckedAllocationException;
024
025/**
026 * Class for VEX Robotics Spike style relay outputs. Relays are intended to be
027 * connected to Spikes or similar relays. The relay channels controls a pair of
028 * pins that are either both off, one on, the other on, or both on. This
029 * translates into two Spike outputs at 0v, one at 12v and one at 0v, one at 0v
030 * and the other at 12v, or two Spike outputs at 12V. This allows off, full
031 * forward, or full reverse control of motors without variable speed. It also
032 * allows the two channels (forward and reverse) to be used independently for
033 * something that does not care about voltage polarity (like a solenoid).
034 */
035public class Relay extends SensorBase implements LiveWindowSendable {
036
037        /**
038         * This class represents errors in trying to set relay values contradictory
039         * to the direction to which the relay is set.
040         */
041        public class InvalidValueException extends RuntimeException {
042
043                /**
044                 * Create a new exception with the given message
045                 *
046                 * @param message
047                 *            the message to pass with the exception
048                 */
049                public InvalidValueException(String message) {
050                        super(message);
051                }
052        }
053
054        /**
055         * The state to drive a Relay to.
056         */
057        public static enum Value {
058                /**
059                 * value: off
060                 */
061                kOff(0),
062                /**
063                 * value: on for relays with defined direction
064                 */
065                kOn(1),
066                /**
067                 * value: forward
068                 */
069                kForward(2),
070                /**
071                 * value: reverse
072                 */
073                kReverse(3);
074
075                /**
076                 * The integer value representing this enumeration
077                 */
078                public final int value;
079
080                private Value(int value) {
081                        this.value = value;
082                }
083        }
084
085        /**
086         * The Direction(s) that a relay is configured to operate in.
087         */
088        public static enum Direction {
089                /**
090                 * direction: both directions are valid
091                 */
092
093                kBoth(0),
094                /**
095                 * direction: Only forward is valid
096                 */
097                kForward(1),
098                /**
099                 * direction: only reverse is valid
100                 */
101                kReverse(2);
102
103                /**
104                 * The integer value representing this enumeration
105                 */
106                public final int value;
107
108                private Direction(int value) {
109                        this.value = value;
110                }
111
112        }
113
114        private final int m_channel;
115        private ByteBuffer m_port;
116
117        private Direction m_direction;
118        private static Resource relayChannels = new Resource(kRelayChannels * 2);
119
120        /**
121         * Common relay initialization method. This code is common to all Relay
122         * constructors and initializes the relay and reserves all resources that
123         * need to be locked. Initially the relay is set to both lines at 0v.
124         */
125        private void initRelay() {
126                SensorBase.checkRelayChannel(m_channel);
127                try {
128                        if (m_direction == Direction.kBoth
129                                        || m_direction == Direction.kForward) {
130                                relayChannels.allocate(m_channel * 2);
131                                UsageReporting.report(tResourceType.kResourceType_Relay, m_channel);
132                        }
133                        if (m_direction == Direction.kBoth
134                                        || m_direction == Direction.kReverse) {
135                                relayChannels.allocate(m_channel * 2 + 1);
136                                UsageReporting.report(tResourceType.kResourceType_Relay, m_channel + 128);
137                        }
138                } catch (CheckedAllocationException e) {
139                        throw new AllocationException("Relay channel " + m_channel + " is already allocated");
140                }
141
142                ByteBuffer status = ByteBuffer.allocateDirect(4);
143                status.order(ByteOrder.LITTLE_ENDIAN);
144
145                m_port = DIOJNI.initializeDigitalPort(DIOJNI.getPort((byte) m_channel), status.asIntBuffer());
146                HALUtil.checkStatus(status.asIntBuffer());
147
148                LiveWindow.addActuator("Relay", m_channel, this);
149        }
150
151        /**
152         * Relay constructor given a channel.
153         *
154         * @param channel
155         *            The channel number for this relay (0 - 3).
156         * @param direction
157         *            The direction that the Relay object will control.
158         */
159        public Relay(final int channel, Direction direction) {
160                if (direction == null)
161                        throw new NullPointerException("Null Direction was given");
162                m_channel = channel;
163                m_direction = direction;
164                initRelay();
165                set(Value.kOff);
166        }
167
168        /**
169         * Relay constructor given a channel, allowing both directions.
170         *
171         * @param channel
172         *            The channel number for this relay (0 - 3).
173         */
174        public Relay(final int channel) {
175                this(channel, Direction.kBoth);
176        }
177
178        @Override
179        public void free() {
180                if (m_direction == Direction.kBoth || m_direction == Direction.kForward) {
181                        relayChannels.free(m_channel*2);
182                }
183                if (m_direction == Direction.kBoth || m_direction == Direction.kReverse) {
184                        relayChannels.free(m_channel*2 + 1);
185                }
186
187                ByteBuffer status = ByteBuffer.allocateDirect(4);
188                status.order(ByteOrder.LITTLE_ENDIAN);
189
190                RelayJNI.setRelayForward(m_port, (byte) 0, status.asIntBuffer());
191                HALUtil.checkStatus(status.asIntBuffer());
192                RelayJNI.setRelayReverse(m_port, (byte) 0, status.asIntBuffer());
193                HALUtil.checkStatus(status.asIntBuffer());
194
195                DIOJNI.freeDIO(m_port, status.asIntBuffer());
196                HALUtil.checkStatus(status.asIntBuffer());
197        }
198
199        /**
200         * Set the relay state.
201         *
202         * Valid values depend on which directions of the relay are controlled by
203         * the object.
204         *
205         * When set to kBothDirections, the relay can be set to any of the four
206         * states: 0v-0v, 12v-0v, 0v-12v, 12v-12v
207         *
208         * When set to kForwardOnly or kReverseOnly, you can specify the constant
209         * for the direction or you can simply specify kOff_val and kOn_val. Using
210         * only kOff_val and kOn_val is recommended.
211         *
212         * @param value
213         *            The state to set the relay.
214         */
215        public void set(Value value) {
216                ByteBuffer status = ByteBuffer.allocateDirect(4);
217                status.order(ByteOrder.LITTLE_ENDIAN);
218
219                switch (value) {
220                case kOff:
221                        if (m_direction == Direction.kBoth
222                                        || m_direction == Direction.kForward) {
223                                RelayJNI.setRelayForward(m_port, (byte) 0, status.asIntBuffer());
224                        }
225                        if (m_direction == Direction.kBoth
226                                        || m_direction == Direction.kReverse) {
227                                RelayJNI.setRelayReverse(m_port, (byte) 0, status.asIntBuffer());
228                        }
229                        break;
230                case kOn:
231                        if (m_direction == Direction.kBoth
232                                        || m_direction == Direction.kForward) {
233                                RelayJNI.setRelayForward(m_port, (byte) 1, status.asIntBuffer());
234                        }
235                        if (m_direction == Direction.kBoth
236                                        || m_direction == Direction.kReverse) {
237                                RelayJNI.setRelayReverse(m_port, (byte) 1, status.asIntBuffer());
238                        }
239                        break;
240                case kForward:
241                        if (m_direction == Direction.kReverse)
242                                throw new InvalidValueException(
243                                                "A relay configured for reverse cannot be set to forward");
244                        if (m_direction == Direction.kBoth
245                                        || m_direction == Direction.kForward) {
246                                RelayJNI.setRelayForward(m_port, (byte) 1, status.asIntBuffer());
247                        }
248                        if (m_direction == Direction.kBoth) {
249                                RelayJNI.setRelayReverse(m_port, (byte) 0, status.asIntBuffer());
250                        }
251                        break;
252                case kReverse:
253                        if (m_direction == Direction.kForward)
254                                throw new InvalidValueException(
255                                                "A relay configured for forward cannot be set to reverse");
256                        if (m_direction == Direction.kBoth) {
257                                RelayJNI.setRelayForward(m_port, (byte) 0, status.asIntBuffer());
258                        }
259                        if (m_direction == Direction.kBoth
260                                        || m_direction == Direction.kReverse) {
261                                RelayJNI.setRelayReverse(m_port, (byte) 1, status.asIntBuffer());
262                        }
263                        break;
264                default:
265                        // Cannot hit this, limited by Value enum
266                }
267
268                HALUtil.checkStatus(status.asIntBuffer());
269        }
270
271        /**
272         * Get the Relay State
273         *
274         * Gets the current state of the relay.
275         *
276         * When set to kForwardOnly or kReverseOnly, value is returned as kOn/kOff
277         * not kForward/kReverse (per the recommendation in Set)
278         *
279         * @return The current state of the relay as a Relay::Value
280         */
281        public Value get() {
282                ByteBuffer status = ByteBuffer.allocateDirect(4);
283                status.order(ByteOrder.LITTLE_ENDIAN);
284
285                if (RelayJNI.getRelayForward(m_port, status.asIntBuffer()) != 0) {
286                        if (RelayJNI.getRelayReverse(m_port, status.asIntBuffer()) != 0) {
287                                return Value.kOn;
288                        } else {
289                                if (m_direction == Direction.kForward) {
290                                        return Value.kOn;
291                                } else {
292                                        return Value.kForward;
293                                }
294                        }
295                } else {
296                        if (RelayJNI.getRelayReverse(m_port, status.asIntBuffer()) != 0) {
297                                if (m_direction == Direction.kReverse) {
298                                        return Value.kOn;
299                                } else {
300                                        return Value.kReverse;
301                                }
302                        } else {
303                                return Value.kOff;
304                        }
305                }
306        }
307
308        /**
309         * Set the Relay Direction
310         *
311         * Changes which values the relay can be set to depending on which direction
312         * is used
313         *
314         * Valid inputs are kBothDirections, kForwardOnly, and kReverseOnly
315         *
316         * @param direction
317         *            The direction for the relay to operate in
318         */
319        public void setDirection(Direction direction) {
320                if (direction == null)
321                        throw new NullPointerException("Null Direction was given");
322                if (m_direction == direction) {
323                        return;
324                }
325
326                free();
327
328                m_direction = direction;
329
330                initRelay();
331        }
332
333        /*
334         * Live Window code, only does anything if live window is activated.
335         */
336        @Override
337        public String getSmartDashboardType() {
338                return "Relay";
339        }
340
341        private ITable m_table;
342        private ITableListener m_table_listener;
343
344        /**
345         * {@inheritDoc}
346         */
347        @Override
348        public void initTable(ITable subtable) {
349                m_table = subtable;
350                updateTable();
351        }
352
353        /**
354         * {@inheritDoc}
355         */
356        @Override
357        public ITable getTable() {
358                return m_table;
359        }
360
361        /**
362         * {@inheritDoc}
363         */
364        @Override
365        public void updateTable() {
366                if (m_table != null) {
367                        if (get() == Value.kOn) {
368                                m_table.putString("Value", "On");
369                        } else if (get() == Value.kForward) {
370                                m_table.putString("Value", "Forward");
371                        } else if (get() == Value.kReverse) {
372                                m_table.putString("Value", "Reverse");
373                        } else {
374                                m_table.putString("Value", "Off");
375                        }
376                }
377        }
378
379        /**
380         * {@inheritDoc}
381         */
382        @Override
383        public void startLiveWindowMode() {
384                m_table_listener = new ITableListener() {
385                        @Override
386                        public void valueChanged(ITable itable, String key, Object value,
387                                        boolean bln) {
388                                String val = ((String) value);
389                                if (val.equals("Off")) {
390                                        set(Value.kOff);
391                                } else if (val.equals("On")) {
392                                        set(Value.kOn);
393                                } else if (val.equals("Forward")) {
394                                        set(Value.kForward);
395                                } else if (val.equals("Reverse")) {
396                                        set(Value.kReverse);
397                                }
398                        }
399                };
400                m_table.addTableListener("Value", m_table_listener, true);
401        }
402
403        /**
404         * {@inheritDoc}
405         */
406        @Override
407        public void stopLiveWindowMode() {
408                // TODO: Broken, should only remove the listener from "Value" only.
409                m_table.removeTableListener(m_table_listener);
410        }
411}