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