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