ControlSystems/SampleCode/Ultrasonic: Ultrasonic2537.java

File Ultrasonic2537.java, 13.7 KB (added by David Albert, 6 years ago)
Line 
1/*----------------------------------------------------------------------------*/
2/* Copyright (c) 2008-2018 FIRST. All Rights Reserved.                        */
3/* Open Source Software - may be modified and shared by FRC teams. The code   */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project.                                                               */
6/*----------------------------------------------------------------------------*/
7
8//package edu.wpi.first.wpilibj;
9package frc.robot;
10
11import java.util.ArrayList;
12import java.util.List;
13import edu.wpi.first.wpilibj.*;
14
15import edu.wpi.first.hal.FRCNetComm.tResourceType;
16import edu.wpi.first.hal.HAL;
17import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder;
18
19import static java.util.Objects.requireNonNull;
20
21/**
22 * Ultrasonic2537 rangefinder class. The Ultrasonic2537 rangefinder measures absolute distance based on the
23 * round-trip time of a ping generated by the controller. These sensors use two transducers, a
24 * speaker and a microphone both tuned to the Ultrasonic2537 range. A common Ultrasonic2537 sensor, the
25 * Daventech SRF04 requires a short pulse to be generated on a digital channel. This causes the
26 * chirp to be emitted. A second line becomes high as the ping is transmitted and goes low when the
27 * echo is received. The time that the line is high determines the round trip distance (time of
28 * flight).
29 */
30public class Ultrasonic2537 extends SendableBase implements PIDSource {
31  /**
32   * The units to return when PIDGet is called.
33   */
34  public enum Unit {
35    /**
36     * Use inches for PIDGet.
37     */
38    kInches,
39    /**
40     * Use millimeters for PIDGet.
41     */
42    kMillimeters
43  }
44
45  // Time (sec) for the ping trigger pulse.
46  private static final double kPingTime = 10 * 1e-6;
47  private static final double kSpeedOfSoundInchesPerSec = 1130.0 * 12.0;
48  // Ultrasonic2537 sensor list
49  private static final List<Ultrasonic2537> m_sensors = new ArrayList<>();
50  // automatic round robin mode
51  private static boolean m_automaticEnabled;
52  private DigitalInput m_echoChannel;
53  private DigitalOutput m_pingChannel;
54  private boolean m_allocatedChannels;
55  private boolean m_enabled;
56  private Counter m_counter;
57  // task doing the round-robin automatic sensing
58  private static Thread m_task;
59  private Unit m_units;
60  private static int m_instances;
61  protected PIDSourceType m_pidSource = PIDSourceType.kDisplacement;
62
63  /**
64   * Background task that goes through the list of Ultrasonic2537 sensors and pings each one in turn.
65   * The counter is configured to read the timing of the returned echo pulse.
66   *
67   * <p><b>DANGER WILL ROBINSON, DANGER WILL ROBINSON:</b> This code runs as a task and assumes that
68   * none of the Ultrasonic2537 sensors will change while it's running. If one does, then this will
69   * certainly break. Make sure to disable automatic mode before changing anything with the
70   * sensors!!
71   */
72  private static class Ultrasonic2537Checker extends Thread {
73    @Override
74    public synchronized void run() {
75      int sensorIndex = 0;
76      Ultrasonic2537 Ultrasonic2537;
77      while (m_automaticEnabled) {
78        //lock list to ensure deletion doesn't occur between empty check and retrieving sensor
79        synchronized (m_sensors) {
80          if (m_sensors.isEmpty()) {
81            return;
82          }
83          //if (sensorIndex >= m_sensors.size()) {
84           // sensorIndex = m_sensors.size() - 1;
85          //}
86          Ultrasonic2537 = m_sensors.get(sensorIndex);
87        }
88        if (Ultrasonic2537.isEnabled()) {
89          // Do the ping
90          Ultrasonic2537.m_pingChannel.pulse(kPingTime);
91        }
92        sensorIndex++;
93        if (sensorIndex >= m_sensors.size()) {
94          sensorIndex = 0;
95        }
96
97        Timer.delay(.1); // wait for ping to return
98      }
99    }
100  }
101
102  /**
103   * Initialize the Ultrasonic2537 Sensor. This is the common code that initializes the Ultrasonic2537
104   * sensor given that there are two digital I/O channels allocated. If the system was running in
105   * automatic mode (round robin) when the new sensor is added, it is stopped, the sensor is added,
106   * then automatic mode is restored.
107   */
108  private synchronized void initialize() {
109    if (m_task == null) {
110      m_task = new Ultrasonic2537Checker();
111    }
112    final boolean originalMode = m_automaticEnabled;
113    setAutomaticMode(false); // kill task when adding a new sensor
114    m_sensors.add(this);
115
116    m_counter = new Counter(m_echoChannel); // set up counter for this
117    addChild(m_counter);
118    // sensor
119    m_counter.setMaxPeriod(1.0);
120    m_counter.setSemiPeriodMode(true);
121    m_counter.reset();
122    m_enabled = true; // make it available for round robin scheduling
123    setAutomaticMode(originalMode);
124
125    m_instances++;
126    HAL.report(tResourceType.kResourceType_Ultrasonic, m_instances);
127    setName("Ultrasonic2537", m_echoChannel.getChannel());
128  }
129
130  /**
131   * Create an instance of the Ultrasonic2537 Sensor. This is designed to supchannel the Daventech SRF04
132   * and Vex Ultrasonic2537 sensors.
133   *
134   * @param pingChannel The digital output channel that sends the pulse to initiate the sensor
135   *                    sending the ping.
136   * @param echoChannel The digital input channel that receives the echo. The length of time that
137   *                    the echo is high represents the round trip time of the ping, and the
138   *                    distance.
139   * @param units       The units returned in either kInches or kMilliMeters
140   */
141  public Ultrasonic2537(final int pingChannel, final int echoChannel, Unit units) {
142    m_pingChannel = new DigitalOutput(pingChannel);
143    m_echoChannel = new DigitalInput(echoChannel);
144    addChild(m_pingChannel);
145    addChild(m_echoChannel);
146    m_allocatedChannels = true;
147    m_units = units;
148    initialize();
149  }
150
151  /**
152   * Create an instance of the Ultrasonic2537 Sensor. This is designed to supchannel the Daventech SRF04
153   * and Vex Ultrasonic2537 sensors. Default unit is inches.
154   *
155   * @param pingChannel The digital output channel that sends the pulse to initiate the sensor
156   *                    sending the ping.
157   * @param echoChannel The digital input channel that receives the echo. The length of time that
158   *                    the echo is high represents the round trip time of the ping, and the
159   *                    distance.
160   */
161  public Ultrasonic2537(final int pingChannel, final int echoChannel) {
162    this(pingChannel, echoChannel, Unit.kInches);
163  }
164
165  /**
166   * Create an instance of an Ultrasonic2537 Sensor from a DigitalInput for the echo channel and a
167   * DigitalOutput for the ping channel.
168   *
169   * @param pingChannel The digital output object that starts the sensor doing a ping. Requires a
170   *                    10uS pulse to start.
171   * @param echoChannel The digital input object that times the return pulse to determine the
172   *                    range.
173   * @param units       The units returned in either kInches or kMilliMeters
174   */
175  public Ultrasonic2537(DigitalOutput pingChannel, DigitalInput echoChannel, Unit units) {
176    requireNonNull(pingChannel, "Provided ping channel was null");
177    requireNonNull(echoChannel, "Provided echo channel was null");
178
179    m_allocatedChannels = false;
180    m_pingChannel = pingChannel;
181    m_echoChannel = echoChannel;
182    m_units = units;
183    initialize();
184  }
185
186  /**
187   * Create an instance of an Ultrasonic2537 Sensor from a DigitalInput for the echo channel and a
188   * DigitalOutput for the ping channel. Default unit is inches.
189   *
190   * @param pingChannel The digital output object that starts the sensor doing a ping. Requires a
191   *                    10uS pulse to start.
192   * @param echoChannel The digital input object that times the return pulse to determine the
193   *                    range.
194   */
195  public Ultrasonic2537(DigitalOutput pingChannel, DigitalInput echoChannel) {
196    this(pingChannel, echoChannel, Unit.kInches);
197  }
198
199  /**
200   * Destructor for the Ultrasonic2537 sensor. Delete the instance of the Ultrasonic2537 sensor by freeing
201   * the allocated digital channels. If the system was in automatic mode (round robin), then it is
202   * stopped, then started again after this sensor is removed (provided this wasn't the last
203   * sensor).
204   */
205  @Override
206  public synchronized void close() {
207    super.close();
208    final boolean wasAutomaticMode = m_automaticEnabled;
209    setAutomaticMode(false);
210    if (m_allocatedChannels) {
211      if (m_pingChannel != null) {
212        m_pingChannel.close();
213      }
214      if (m_echoChannel != null) {
215        m_echoChannel.close();
216      }
217    }
218
219    if (m_counter != null) {
220      m_counter.close();
221      m_counter = null;
222    }
223
224    m_pingChannel = null;
225    m_echoChannel = null;
226    synchronized (m_sensors) {
227      m_sensors.remove(this);
228    }
229    if (!m_sensors.isEmpty() && wasAutomaticMode) {
230      setAutomaticMode(true);
231    }
232  }
233
234  /**
235   * Turn Automatic mode on/off. When in Automatic mode, all sensors will fire in round robin,
236   * waiting a set time between each sensor.
237   *
238   * @param enabling Set to true if round robin scheduling should start for all the Ultrasonic2537
239   *                 sensors. This scheduling method assures that the sensors are non-interfering
240   *                 because no two sensors fire at the same time. If another scheduling algorithm
241   *                 is preferred, it can be implemented by pinging the sensors manually and waiting
242   *                 for the results to come back.
243   */
244  public void setAutomaticMode(boolean enabling) {
245    if (enabling == m_automaticEnabled) {
246      return; // ignore the case of no change
247    }
248    m_automaticEnabled = enabling;
249
250    if (enabling) {
251      /* Clear all the counters so no data is valid. No synchronization is
252       * needed because the background task is stopped.
253       */
254      for (Ultrasonic2537 u : m_sensors) {
255        u.m_counter.reset();
256      }
257
258      // Start round robin task
259      m_task.start();
260    } else {
261      // Wait for background task to stop running
262      try {
263        m_task.join();
264      } catch (InterruptedException ex) {
265        Thread.currentThread().interrupt();
266        ex.printStackTrace();
267      }
268
269      /* Clear all the counters (data now invalid) since automatic mode is
270       * disabled. No synchronization is needed because the background task is
271       * stopped.
272       */
273      for (Ultrasonic2537 u : m_sensors) {
274        u.m_counter.reset();
275      }
276    }
277  }
278
279  /**
280   * Single ping to Ultrasonic2537 sensor. Send out a single ping to the Ultrasonic2537 sensor. This only
281   * works if automatic (round robin) mode is disabled. A single ping is sent out, and the counter
282   * should count the semi-period when it comes in. The counter is reset to make the current value
283   * invalid.
284   */
285  public void ping() {
286    setAutomaticMode(false); // turn off automatic round robin if pinging
287    // single sensor
288    m_counter.reset(); // reset the counter to zero (invalid data now)
289    // do the ping to start getting a single range
290    m_pingChannel.pulse(kPingTime);
291  }
292
293  /**
294   * Check if there is a valid range measurement. The ranges are accumulated in a counter that will
295   * increment on each edge of the echo (return) signal. If the count is not at least 2, then the
296   * range has not yet been measured, and is invalid.
297   *
298   * @return true if the range is valid
299   */
300  public boolean isRangeValid() {
301    return m_counter.get() > 1;
302  }
303
304  /**
305   * Get the range in inches from the Ultrasonic2537 sensor. If there is no valid value yet, i.e. at
306   * least one measurement hasn't completed, then return 0.
307   *
308   * @return double Range in inches of the target returned from the Ultrasonic2537 sensor.
309   */
310  public double getRangeInches() {
311    if (isRangeValid()) {
312      return m_counter.getPeriod() * kSpeedOfSoundInchesPerSec / 2.0;
313    } else {
314      return 0;
315    }
316  }
317
318  /**
319   * Get the range in millimeters from the Ultrasonic2537 sensor. If there is no valid value yet, i.e.
320   * at least one measurement hasn't completed, then return 0.
321   *
322   * @return double Range in millimeters of the target returned by the Ultrasonic2537 sensor.
323   */
324  public double getRangeMM() {
325    return getRangeInches() * 25.4;
326  }
327
328  @Override
329  public void setPIDSourceType(PIDSourceType pidSource) {
330    if (!pidSource.equals(PIDSourceType.kDisplacement)) {
331      throw new IllegalArgumentException("Only displacement PID is allowed for Ultrasonic2537s.");
332    }
333    m_pidSource = pidSource;
334  }
335
336  @Override
337  public PIDSourceType getPIDSourceType() {
338    return m_pidSource;
339  }
340
341  /**
342   * Get the range in the current DistanceUnit for the PIDSource base object.
343   *
344   * @return The range in DistanceUnit
345   */
346  @Override
347  public double pidGet() {
348    switch (m_units) {
349      case kInches:
350        return getRangeInches();
351      case kMillimeters:
352        return getRangeMM();
353      default:
354        return 0.0;
355    }
356  }
357
358  /**
359   * Set the current DistanceUnit that should be used for the PIDSource base object.
360   *
361   * @param units The DistanceUnit that should be used.
362   */
363  public void setDistanceUnits(Unit units) {
364    m_units = units;
365  }
366
367  /**
368   * Get the current DistanceUnit that is used for the PIDSource base object.
369   *
370   * @return The type of DistanceUnit that is being used.
371   */
372  public Unit getDistanceUnits() {
373    return m_units;
374  }
375
376  /**
377   * Is the Ultrasonic2537 enabled.
378   *
379   * @return true if the Ultrasonic2537 is enabled
380   */
381  public boolean isEnabled() {
382    return m_enabled;
383  }
384
385  /**
386   * Set if the Ultrasonic2537 is enabled.
387   *
388   * @param enable set to true to enable the Ultrasonic2537
389   */
390  public void setEnabled(boolean enable) {
391    m_enabled = enable;
392  }
393
394  @Override
395  public void initSendable(SendableBuilder builder) {
396    builder.setSmartDashboardType("Ultrasonic2537");
397    builder.addDoubleProperty("Value", this::getRangeInches, null);
398  }
399}