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 edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType;
011import edu.wpi.first.wpilibj.communication.UsageReporting;
012import edu.wpi.first.wpilibj.Timer;
013import edu.wpi.first.wpilibj.livewindow.LiveWindow;
014import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
015import edu.wpi.first.wpilibj.tables.ITable;
016
017/**
018 * Ultrasonic rangefinder class. The Ultrasonic rangefinder measures absolute
019 * distance based on the round-trip time of a ping generated by the controller.
020 * These sensors use two transducers, a speaker and a microphone both tuned to
021 * the ultrasonic range. A common ultrasonic sensor, the Daventech SRF04
022 * requires a short pulse to be generated on a digital channel. This causes the
023 * chirp to be emmitted. A second line becomes high as the ping is transmitted
024 * and goes low when the echo is received. The time that the line is high
025 * determines the round trip distance (time of flight).
026 */
027public class Ultrasonic extends SensorBase implements PIDSource, LiveWindowSendable {
028
029        /**
030         * The units to return when PIDGet is called
031         */
032        public static class Unit {
033
034                /**
035                 * The integer value representing this enumeration
036                 */
037                public final int value;
038                static final int kInches_val = 0;
039                static final int kMillimeters_val = 1;
040                /**
041                 * Use inches for PIDGet
042                 */
043                public static final Unit kInches = new Unit(kInches_val);
044                /**
045                 * Use millimeters for PIDGet
046                 */
047                public static final Unit kMillimeter = new Unit(kMillimeters_val);
048
049                private Unit(int value) {
050                        this.value = value;
051                }
052        }
053
054        private static final double kPingTime = 10 * 1e-6; // /< Time (sec) for the
055                                                                                                                // ping trigger pulse.
056        private static final int kPriority = 90; // /< Priority that the ultrasonic
057                                                                                                // round robin task runs.
058        private static final double kMaxUltrasonicTime = 0.1; // /< Max time (ms)
059                                                                                                                        // between readings.
060        private static final double kSpeedOfSoundInchesPerSec = 1130.0 * 12.0;
061        private static Ultrasonic m_firstSensor = null; // head of the ultrasonic
062                                                                                                        // sensor list
063        private static boolean m_automaticEnabled = false; // automatic round robin
064                                                                                                                // mode
065        private DigitalInput m_echoChannel = null;
066        private DigitalOutput m_pingChannel = null;
067        private boolean m_allocatedChannels;
068        private boolean m_enabled = false;
069        private Counter m_counter = null;
070        private Ultrasonic m_nextSensor = null;
071        private static Thread m_task = null; // task doing the round-robin automatic
072                                                                                        // sensing
073        private Unit m_units;
074        private static int m_instances = 0;
075
076        /**
077         * Background task that goes through the list of ultrasonic sensors and
078         * pings each one in turn. The counter is configured to read the timing of
079         * the returned echo pulse.
080         *
081         * DANGER WILL ROBINSON, DANGER WILL ROBINSON: This code runs as a task and
082         * assumes that none of the ultrasonic sensors will change while it's
083         * running. If one does, then this will certainly break. Make sure to
084         * disable automatic mode before changing anything with the sensors!!
085         */
086        private class UltrasonicChecker extends Thread {
087
088                public synchronized void run() {
089                        Ultrasonic u = null;
090                        while (m_automaticEnabled) {
091                                if (u == null) {
092                                        u = m_firstSensor;
093                                }
094                                if (u == null) {
095                                        return;
096                                }
097                                if (u.isEnabled()) {
098                                        u.m_pingChannel.pulse(m_pingChannel.m_channel,
099                                                        (float) kPingTime); // do the ping
100                                }
101                                u = u.m_nextSensor;
102                                Timer.delay(.1); // wait for ping to return
103                        }
104                }
105        }
106
107        /**
108         * Initialize the Ultrasonic Sensor. This is the common code that
109         * initializes the ultrasonic sensor given that there are two digital I/O
110         * channels allocated. If the system was running in automatic mode (round
111         * robin) when the new sensor is added, it is stopped, the sensor is added,
112         * then automatic mode is restored.
113         */
114        private synchronized void initialize() {
115                if (m_task == null) {
116                        m_task = new UltrasonicChecker();
117                }
118                boolean originalMode = m_automaticEnabled;
119                setAutomaticMode(false); // kill task when adding a new sensor
120                m_nextSensor = m_firstSensor;
121                m_firstSensor = this;
122
123                m_counter = new Counter(m_echoChannel); // set up counter for this
124                                                                                                // sensor
125                m_counter.setMaxPeriod(1.0);
126                m_counter.setSemiPeriodMode(true);
127                m_counter.reset();
128                m_enabled = true; // make it available for round robin scheduling
129                setAutomaticMode(originalMode);
130
131                m_instances++;
132                UsageReporting.report(tResourceType.kResourceType_Ultrasonic,
133                                m_instances);
134                LiveWindow.addSensor("Ultrasonic", m_echoChannel.getChannel(), this);
135        }
136
137        /**
138         * Create an instance of the Ultrasonic Sensor.
139         * This is designed to supchannel the Daventech SRF04 and Vex ultrasonic
140         * sensors.
141         *
142         * @param pingChannel
143         *            The digital output channel that sends the pulse to initiate
144         *            the sensor sending the ping.
145         * @param echoChannel
146         *            The digital input channel that receives the echo. The length
147         *            of time that the echo is high represents the round trip time
148         *            of the ping, and the distance.
149         * @param units
150         *            The units returned in either kInches or kMilliMeters
151         */
152        public Ultrasonic(final int pingChannel, final int echoChannel, Unit units) {
153                m_pingChannel = new DigitalOutput(pingChannel);
154                m_echoChannel = new DigitalInput(echoChannel);
155                m_allocatedChannels = true;
156                m_units = units;
157                initialize();
158        }
159
160        /**
161         * Create an instance of the Ultrasonic Sensor.
162         * This is designed to supchannel the Daventech SRF04 and Vex ultrasonic
163         * sensors. Default unit is inches.
164         *
165         * @param pingChannel
166         *            The digital output channel that sends the pulse to initiate
167         *            the sensor sending the ping.
168         * @param echoChannel
169         *            The digital input channel that receives the echo. The length
170         *            of time that the echo is high represents the round trip time
171         *            of the ping, and the distance.
172         */
173        public Ultrasonic(final int pingChannel, final int echoChannel) {
174                this(pingChannel, echoChannel, Unit.kInches);
175        }
176
177        /**
178         * Create an instance of an Ultrasonic Sensor from a DigitalInput for the
179         * echo channel and a DigitalOutput for the ping channel.
180         *
181         * @param pingChannel
182         *            The digital output object that starts the sensor doing a ping.
183         *            Requires a 10uS pulse to start.
184         * @param echoChannel
185         *            The digital input object that times the return pulse to
186         *            determine the range.
187         * @param units
188         *            The units returned in either kInches or kMilliMeters
189         */
190        public Ultrasonic(DigitalOutput pingChannel, DigitalInput echoChannel,
191                        Unit units) {
192                if (pingChannel == null || echoChannel == null) {
193                        throw new NullPointerException("Null Channel Provided");
194                }
195                m_allocatedChannels = false;
196                m_pingChannel = pingChannel;
197                m_echoChannel = echoChannel;
198                m_units = units;
199                initialize();
200        }
201
202        /**
203         * Create an instance of an Ultrasonic Sensor from a DigitalInput for the
204         * echo channel and a DigitalOutput for the ping channel. Default unit is
205         * inches.
206         *
207         * @param pingChannel
208         *            The digital output object that starts the sensor doing a ping.
209         *            Requires a 10uS pulse to start.
210         * @param echoChannel
211         *            The digital input object that times the return pulse to
212         *            determine the range.
213         */
214        public Ultrasonic(DigitalOutput pingChannel, DigitalInput echoChannel) {
215                this(pingChannel, echoChannel, Unit.kInches);
216        }
217
218        /**
219         * Destructor for the ultrasonic sensor. Delete the instance of the
220         * ultrasonic sensor by freeing the allocated digital channels. If the
221         * system was in automatic mode (round robin), then it is stopped, then
222         * started again after this sensor is removed (provided this wasn't the last
223         * sensor).
224         */
225        public synchronized void free() {
226                boolean wasAutomaticMode = m_automaticEnabled;
227                setAutomaticMode(false);
228                if (m_allocatedChannels) {
229                        if (m_pingChannel != null) {
230                                m_pingChannel.free();
231                        }
232                        if (m_echoChannel != null) {
233                                m_echoChannel.free();
234                        }
235                }
236
237                if (m_counter != null) {
238                        m_counter.free();
239                        m_counter = null;
240                }
241
242                m_pingChannel = null;
243                m_echoChannel = null;
244
245                if (this == m_firstSensor) {
246                        m_firstSensor = m_nextSensor;
247                        if (m_firstSensor == null) {
248                                setAutomaticMode(false);
249                        }
250                } else {
251                        for (Ultrasonic s = m_firstSensor; s != null; s = s.m_nextSensor) {
252                                if (this == s.m_nextSensor) {
253                                        s.m_nextSensor = s.m_nextSensor.m_nextSensor;
254                                        break;
255                                }
256                        }
257                }
258                if (m_firstSensor != null && wasAutomaticMode) {
259                        setAutomaticMode(true);
260                }
261        }
262
263        /**
264         * Turn Automatic mode on/off. When in Automatic mode, all sensors will fire
265         * in round robin, waiting a set time between each sensor.
266         *
267         * @param enabling
268         *            Set to true if round robin scheduling should start for all the
269         *            ultrasonic sensors. This scheduling method assures that the
270         *            sensors are non-interfering because no two sensors fire at the
271         *            same time. If another scheduling algorithm is preffered, it
272         *            can be implemented by pinging the sensors manually and waiting
273         *            for the results to come back.
274         */
275        public void setAutomaticMode(boolean enabling) {
276                if (enabling == m_automaticEnabled) {
277                        return; // ignore the case of no change
278                }
279                m_automaticEnabled = enabling;
280
281                if (enabling) {
282                        // enabling automatic mode.
283                        // Clear all the counters so no data is valid
284                        for (Ultrasonic u = m_firstSensor; u != null; u = u.m_nextSensor) {
285                                u.m_counter.reset();
286                        }
287                        // Start round robin task
288                        m_task.start();
289                } else {
290                        // disabling automatic mode. Wait for background task to stop
291                        // running.
292                        while (m_task.isAlive()) {
293                                Timer.delay(.15); // just a little longer than the ping time for
294                                                                        // round-robin to stop
295                        }
296                        // clear all the counters (data now invalid) since automatic mode is
297                        // stopped
298                        for (Ultrasonic u = m_firstSensor; u != null; u = u.m_nextSensor) {
299                                u.m_counter.reset();
300                        }
301                }
302        }
303
304        /**
305         * Single ping to ultrasonic sensor. Send out a single ping to the
306         * ultrasonic sensor. This only works if automatic (round robin) mode is
307         * disabled. A single ping is sent out, and the counter should count the
308         * semi-period when it comes in. The counter is reset to make the current
309         * value invalid.
310         */
311        public void ping() {
312                setAutomaticMode(false); // turn off automatic round robin if pinging
313                                                                        // single sensor
314                m_counter.reset(); // reset the counter to zero (invalid data now)
315                m_pingChannel.pulse(m_pingChannel.m_channel, (float) kPingTime); // do
316                                                                                                                                                        // the
317                                                                                                                                                        // ping
318                                                                                                                                                        // to
319                                                                                                                                                        // start
320                                                                                                                                                        // getting
321                                                                                                                                                        // a
322                                                                                                                                                        // single
323                                                                                                                                                        // range
324        }
325
326        /**
327         * Check if there is a valid range measurement. The ranges are accumulated
328         * in a counter that will increment on each edge of the echo (return)
329         * signal. If the count is not at least 2, then the range has not yet been
330         * measured, and is invalid.
331         *
332         * @return true if the range is valid
333         */
334        public boolean isRangeValid() {
335                return m_counter.get() > 1;
336        }
337
338        /**
339         * Get the range in inches from the ultrasonic sensor.
340         *
341         * @return double Range in inches of the target returned from the ultrasonic
342         *         sensor. If there is no valid value yet, i.e. at least one
343         *         measurement hasn't completed, then return 0.
344         */
345        public double getRangeInches() {
346                if (isRangeValid()) {
347                        return m_counter.getPeriod() * kSpeedOfSoundInchesPerSec / 2.0;
348                } else {
349                        return 0;
350                }
351        }
352
353        /**
354         * Get the range in millimeters from the ultrasonic sensor.
355         *
356         * @return double Range in millimeters of the target returned by the
357         *         ultrasonic sensor. If there is no valid value yet, i.e. at least
358         *         one measurement hasn't complted, then return 0.
359         */
360        public double getRangeMM() {
361                return getRangeInches() * 25.4;
362        }
363
364        /**
365         * Get the range in the current DistanceUnit for the PIDSource base object.
366         *
367         * @return The range in DistanceUnit
368         */
369        public double pidGet() {
370                switch (m_units.value) {
371                case Unit.kInches_val:
372                        return getRangeInches();
373                case Unit.kMillimeters_val:
374                        return getRangeMM();
375                default:
376                        return 0.0;
377                }
378        }
379
380        /**
381         * Set the current DistanceUnit that should be used for the PIDSource base
382         * object.
383         *
384         * @param units
385         *            The DistanceUnit that should be used.
386         */
387        public void setDistanceUnits(Unit units) {
388                m_units = units;
389        }
390
391        /**
392         * Get the current DistanceUnit that is used for the PIDSource base object.
393         *
394         * @return The type of DistanceUnit that is being used.
395         */
396        public Unit getDistanceUnits() {
397                return m_units;
398        }
399
400        /**
401         * Is the ultrasonic enabled
402         *
403         * @return true if the ultrasonic is enabled
404         */
405        public boolean isEnabled() {
406                return m_enabled;
407        }
408
409        /**
410         * Set if the ultrasonic is enabled
411         *
412         * @param enable
413         *            set to true to enable the ultrasonic
414         */
415        public void setEnabled(boolean enable) {
416                m_enabled = enable;
417        }
418
419        /*
420         * Live Window code, only does anything if live window is activated.
421         */
422        public String getSmartDashboardType() {
423                return "Ultrasonic";
424        }
425
426        private ITable m_table;
427
428        /**
429         * {@inheritDoc}
430         */
431        public void initTable(ITable subtable) {
432                m_table = subtable;
433                updateTable();
434        }
435
436        /**
437         * {@inheritDoc}
438         */
439        public ITable getTable() {
440                return m_table;
441        }
442
443        /**
444         * {@inheritDoc}
445         */
446        public void updateTable() {
447                if (m_table != null) {
448                        m_table.putNumber("Value", getRangeInches());
449                }
450        }
451
452        /**
453         * {@inheritDoc}
454         */
455        public void startLiveWindowMode() {
456        }
457
458        /**
459         * {@inheritDoc}
460         */
461        public void stopLiveWindowMode() {
462        }
463}