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.hal.HALUtil;
014import edu.wpi.first.wpilibj.hal.InterruptJNI;
015import edu.wpi.first.wpilibj.util.AllocationException;
016import edu.wpi.first.wpilibj.util.CheckedAllocationException;
017
018/**
019 * Base for sensors to be used with interrupts
020 */
021public abstract class InterruptableSensorBase extends SensorBase {
022        /**
023         * This is done to store the JVM variable in the InterruptJNI
024         * This is done because the HAL must have access to the JVM variable
025         * in order to attach the newly spawned thread when an interrupt is fired.
026         */
027        static{
028                ByteBuffer status = ByteBuffer.allocateDirect(4);
029                status.order(ByteOrder.LITTLE_ENDIAN);
030                InterruptJNI.initializeInterruptJVM(status.asIntBuffer());
031                HALUtil.checkStatus(status.asIntBuffer());
032        }
033
034        /**
035         * The interrupt resource
036         */
037        protected ByteBuffer m_interrupt = null;
038
039        /**
040         * Flags if the interrupt being allocated is synchronous
041         */
042        protected boolean m_isSynchronousInterrupt = false;
043
044        /**
045         * The index of the interrupt
046         */
047        protected int m_interruptIndex;
048        /**
049         * Resource manager
050         */
051        protected static Resource interrupts = new Resource(8);
052
053        /**
054         * Create a new InterrupatableSensorBase
055         */
056        public InterruptableSensorBase() {
057                m_interrupt = null;
058        }
059
060        /**
061         * @return true if this is an analog trigger
062         */
063        abstract boolean getAnalogTriggerForRouting();
064
065        /**
066         * @return channel routing number
067         */
068        abstract int getChannelForRouting();
069
070        /**
071         * @return module routing number
072         */
073        abstract byte getModuleForRouting();
074
075        /**
076         * Request one of the 8 interrupts asynchronously on this digital input.
077         *
078         * @param handler
079         *            The {@link InterruptHandlerFunction} that contains the method
080         *            {@link InterruptHandlerFunction#interruptFired(int, Object)} that
081         *            will be called whenever there is an interrupt on this device.
082         *            Request interrupts in synchronous mode where the user program
083         *            interrupt handler will be called when an interrupt occurs. The
084         *            default is interrupt on rising edges only.
085         */
086        public void requestInterrupts(InterruptHandlerFunction<?> handler) {
087                if(m_interrupt != null){
088                        throw new AllocationException("The interrupt has already been allocated");
089                }
090
091                allocateInterrupts(false);
092
093                assert (m_interrupt != null);
094
095                ByteBuffer status = ByteBuffer.allocateDirect(4);
096                // set the byte order
097                status.order(ByteOrder.LITTLE_ENDIAN);
098                InterruptJNI.requestInterrupts(m_interrupt, getModuleForRouting(),
099                                getChannelForRouting(),
100                                (byte) (getAnalogTriggerForRouting() ? 1 : 0), status.asIntBuffer());
101                HALUtil.checkStatus(status.asIntBuffer());
102                setUpSourceEdge(true, false);
103                InterruptJNI.attachInterruptHandler(m_interrupt, handler.function, handler.overridableParamater(), status.asIntBuffer());
104                HALUtil.checkStatus(status.asIntBuffer());
105        }
106
107        /**
108         * Request one of the 8 interrupts synchronously on this digital input. Request
109         * interrupts in synchronous mode where the user program will have to
110         * explicitly wait for the interrupt to occur using {@link #waitForInterrupt}. 
111         * The default is interrupt on rising edges only.
112         */
113        public void requestInterrupts() {
114                if(m_interrupt != null){
115                        throw new AllocationException("The interrupt has already been allocated");
116                }
117
118                allocateInterrupts(true);
119
120                assert (m_interrupt != null);
121
122                ByteBuffer status = ByteBuffer.allocateDirect(4);
123                // set the byte order
124                status.order(ByteOrder.LITTLE_ENDIAN);
125                InterruptJNI.requestInterrupts(m_interrupt, getModuleForRouting(),
126                                getChannelForRouting(),
127                                (byte) (getAnalogTriggerForRouting() ? 1 : 0), status.asIntBuffer());
128                HALUtil.checkStatus(status.asIntBuffer());
129                setUpSourceEdge(true, false);
130
131        }
132
133        /**
134         * Allocate the interrupt
135         *
136         * @param watcher true if the interrupt should be in synchronous mode where the user
137         * program will have to explicitly wait for the interrupt to occur.
138         */
139        protected void allocateInterrupts(boolean watcher) {
140                try {
141                        m_interruptIndex = interrupts.allocate();
142                } catch (CheckedAllocationException e) {
143                        throw new AllocationException(
144                                        "No interrupts are left to be allocated");
145                }
146                m_isSynchronousInterrupt = watcher;
147
148                ByteBuffer status = ByteBuffer.allocateDirect(4);
149                status.order(ByteOrder.LITTLE_ENDIAN);
150                m_interrupt = InterruptJNI.initializeInterrupts(m_interruptIndex,
151                                (byte) (watcher ? 1 : 0), status.asIntBuffer());
152                HALUtil.checkStatus(status.asIntBuffer());
153        }
154
155        /**
156         * Cancel interrupts on this device. This deallocates all the chipobject
157         * structures and disables any interrupts.
158         */
159        public void cancelInterrupts() {
160                if (m_interrupt == null) {
161                        throw new IllegalStateException("The interrupt is not allocated.");
162                }
163                ByteBuffer status = ByteBuffer.allocateDirect(4);
164                status.order(ByteOrder.LITTLE_ENDIAN);
165                InterruptJNI.cleanInterrupts(m_interrupt, status.asIntBuffer());
166                HALUtil.checkStatus(status.asIntBuffer());
167                m_interrupt = null;
168                interrupts.free(m_interruptIndex);
169        }
170
171        /**
172         * In synchronous mode, wait for the defined interrupt to occur.
173         *
174         * @param timeout
175         *            Timeout in seconds
176         * @param ignorePrevious
177         *            If true, ignore interrupts that happened before
178         *            waitForInterrupt was called.
179         */
180        public void waitForInterrupt(double timeout, boolean ignorePrevious) {
181                if (m_interrupt == null) {
182                        throw new IllegalStateException("The interrupt is not allocated.");
183                }
184                ByteBuffer status = ByteBuffer.allocateDirect(4);
185                status.order(ByteOrder.LITTLE_ENDIAN);
186                InterruptJNI.waitForInterrupt(m_interrupt, (float) timeout, ignorePrevious, status.asIntBuffer());
187                HALUtil.checkStatus(status.asIntBuffer());
188        }
189
190        /**
191         * In synchronous mode, wait for the defined interrupt to occur.
192         *
193         * @param timeout
194         *            Timeout in seconds
195         */
196        public void waitForInterrupt(double timeout) {
197                waitForInterrupt(timeout, true);
198        }
199
200        /**
201         * Enable interrupts to occur on this input. Interrupts are disabled when
202         * the RequestInterrupt call is made. This gives time to do the setup of the
203         * other options before starting to field interrupts.
204         */
205        public void enableInterrupts() {
206                if (m_interrupt == null) {
207                        throw new IllegalStateException("The interrupt is not allocated.");
208                }
209                if(m_isSynchronousInterrupt){
210                        throw new IllegalStateException("You do not need to enable synchronous interrupts");
211                }
212                ByteBuffer status = ByteBuffer.allocateDirect(4);
213                status.order(ByteOrder.LITTLE_ENDIAN);
214                InterruptJNI.enableInterrupts(m_interrupt, status.asIntBuffer());
215                HALUtil.checkStatus(status.asIntBuffer());
216        }
217
218        /**
219         * Disable Interrupts without without deallocating structures.
220         */
221        public void disableInterrupts() {
222                if (m_interrupt == null) {
223                        throw new IllegalStateException("The interrupt is not allocated.");
224                }
225                if(m_isSynchronousInterrupt){
226                        throw new IllegalStateException("You can not disable synchronous interrupts");
227                }
228                ByteBuffer status = ByteBuffer.allocateDirect(4);
229                status.order(ByteOrder.LITTLE_ENDIAN);
230                InterruptJNI.disableInterrupts(m_interrupt, status.asIntBuffer());
231                HALUtil.checkStatus(status.asIntBuffer());
232        }
233
234        /**
235         * Return the timestamp for the rising interrupt that occurred most
236         * recently. This is in the same time domain as getClock().
237         * The rising-edge interrupt should be enabled with
238         * {@link #setUpSourceEdge}
239         * @return Timestamp in seconds since boot.
240         */
241        public double readRisingTimestamp() {
242                if (m_interrupt == null) {
243                        throw new IllegalStateException("The interrupt is not allocated.");
244                }
245                ByteBuffer status = ByteBuffer.allocateDirect(4);
246                status.order(ByteOrder.LITTLE_ENDIAN);
247                double timestamp = InterruptJNI.readRisingTimestamp(m_interrupt, status.asIntBuffer());
248                HALUtil.checkStatus(status.asIntBuffer());
249                return timestamp;
250        }
251
252        /**
253        * Return the timestamp for the falling interrupt that occurred most
254        * recently. This is in the same time domain as getClock().
255        * The falling-edge interrupt should be enabled with
256        * {@link #setUpSourceEdge}
257        * @return Timestamp in seconds since boot.
258        */
259        public double readFallingTimestamp() {
260                if (m_interrupt == null) {
261                        throw new IllegalStateException("The interrupt is not allocated.");
262                }
263                ByteBuffer status = ByteBuffer.allocateDirect(4);
264                status.order(ByteOrder.LITTLE_ENDIAN);
265                double timestamp = InterruptJNI.readFallingTimestamp(m_interrupt, status.asIntBuffer());
266                HALUtil.checkStatus(status.asIntBuffer());
267                return timestamp;
268        }
269
270        /**
271         * Set which edge to trigger interrupts on
272         *
273         * @param risingEdge
274         *            true to interrupt on rising edge
275         * @param fallingEdge
276         *            true to interrupt on falling edge
277         */
278        public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
279                if (m_interrupt != null) {
280                        ByteBuffer status = ByteBuffer.allocateDirect(4);
281                        // set the byte order
282                        status.order(ByteOrder.LITTLE_ENDIAN);
283                        InterruptJNI.setInterruptUpSourceEdge(m_interrupt,
284                                        (byte) (risingEdge ? 1 : 0), (byte) (fallingEdge ? 1 : 0),
285                                        status.asIntBuffer());
286                        HALUtil.checkStatus(status.asIntBuffer());
287                } else {
288                        throw new IllegalArgumentException(
289                                        "You must call RequestInterrupts before setUpSourceEdge");
290                }
291        }
292}