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 java.nio.ByteBuffer;
011import java.nio.ByteOrder;
012
013import edu.wpi.first.wpilibj.AnalogTriggerOutput.AnalogTriggerType;
014import edu.wpi.first.wpilibj.hal.CounterJNI;
015import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType;
016import edu.wpi.first.wpilibj.hal.HAL;
017import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
018import edu.wpi.first.wpilibj.tables.ITable;
019import edu.wpi.first.wpilibj.util.BoundaryException;
020
021/**
022 * Class for counting the number of ticks on a digital input channel. This is a general purpose
023 * class for counting repetitive events. It can return the number of counts, the period of the most
024 * recent cycle, and detect when the signal being counted has stopped by supplying a maximum cycle
025 * time.
026 *
027 * <p>All counters will immediately start counting - reset() them if you need them to be zeroed
028 * before use.
029 */
030public class Counter extends SensorBase implements CounterBase, LiveWindowSendable, PIDSource {
031
032  /**
033   * Mode determines how and what the counter counts.
034   */
035  public enum Mode {
036    /**
037     * mode: two pulse.
038     */
039    kTwoPulse(0),
040    /**
041     * mode: semi period.
042     */
043    kSemiperiod(1),
044    /**
045     * mode: pulse length.
046     */
047    kPulseLength(2),
048    /**
049     * mode: external direction.
050     */
051    kExternalDirection(3);
052
053    @SuppressWarnings("MemberName")
054    public final int value;
055
056    private Mode(int value) {
057      this.value = value;
058    }
059  }
060
061  protected DigitalSource m_upSource; // /< What makes the counter count up.
062  protected DigitalSource m_downSource; // /< What makes the counter count down.
063  private boolean m_allocatedUpSource;
064  private boolean m_allocatedDownSource;
065  private int m_counter; // /< The FPGA counter object.
066  private int m_index; // /< The index of this counter.
067  private PIDSourceType m_pidSource;
068  private double m_distancePerPulse; // distance of travel for each tick
069
070  /**
071   * Create an instance of a counter with the given mode.
072   */
073  public Counter(final Mode mode) {
074    ByteBuffer index = ByteBuffer.allocateDirect(4);
075    // set the byte order
076    index.order(ByteOrder.LITTLE_ENDIAN);
077    m_counter = CounterJNI.initializeCounter(mode.value, index.asIntBuffer());
078    m_index = index.asIntBuffer().get(0);
079
080    m_allocatedUpSource = false;
081    m_allocatedDownSource = false;
082    m_upSource = null;
083    m_downSource = null;
084
085    setMaxPeriod(.5);
086
087    HAL.report(tResourceType.kResourceType_Counter, m_index, mode.value);
088  }
089
090  /**
091   * Create an instance of a counter where no sources are selected. Then they all must be selected
092   * by calling functions to specify the upsource and the downsource independently.
093   *
094   * <p>The counter will start counting immediately.
095   */
096  public Counter() {
097    this(Mode.kTwoPulse);
098  }
099
100  /**
101   * Create an instance of a counter from a Digital Input. This is used if an existing digital input
102   * is to be shared by multiple other objects such as encoders or if the Digital Source is not a
103   * DIO channel (such as an Analog Trigger)
104   *
105   * <p>The counter will start counting immediately.
106   *
107   * @param source the digital source to count
108   */
109  public Counter(DigitalSource source) {
110    this();
111    if (source == null) {
112      throw new NullPointerException("Digital Source given was null");
113    }
114    setUpSource(source);
115  }
116
117  /**
118   * Create an instance of a Counter object. Create an up-Counter instance given a channel.
119   *
120   * <p>The counter will start counting immediately.
121   *
122   * @param channel the DIO channel to use as the up source. 0-9 are on-board, 10-25 are on the MXP
123   */
124  public Counter(int channel) {
125    this();
126    setUpSource(channel);
127  }
128
129  /**
130   * Create an instance of a Counter object. Create an instance of a simple up-Counter given an
131   * analog trigger. Use the trigger state output from the analog trigger.
132   *
133   * <p>The counter will start counting immediately.
134   *
135   * @param encodingType which edges to count
136   * @param upSource     first source to count
137   * @param downSource   second source for direction
138   * @param inverted     true to invert the count
139   */
140  public Counter(EncodingType encodingType, DigitalSource upSource, DigitalSource downSource,
141                 boolean inverted) {
142    this(Mode.kExternalDirection);
143    if (encodingType == null) {
144      throw new NullPointerException("Encoding type given was null");
145    }
146    if (encodingType != EncodingType.k1X && encodingType != EncodingType.k2X) {
147      throw new RuntimeException("Counters only support 1X and 2X quadreature decoding!");
148    }
149    if (upSource == null) {
150      throw new NullPointerException("Up Source given was null");
151    }
152    setUpSource(upSource);
153    if (downSource == null) {
154      throw new NullPointerException("Down Source given was null");
155    }
156    setDownSource(downSource);
157
158    if (encodingType == EncodingType.k1X) {
159      setUpSourceEdge(true, false);
160      CounterJNI.setCounterAverageSize(m_counter, 1);
161    } else {
162      setUpSourceEdge(true, true);
163      CounterJNI.setCounterAverageSize(m_counter, 2);
164    }
165
166    setDownSourceEdge(inverted, true);
167  }
168
169  /**
170   * Create an instance of a Counter object. Create an instance of a simple up-Counter given an
171   * analog trigger. Use the trigger state output from the analog trigger.
172   *
173   * <p>The counter will start counting immediately.
174   *
175   * @param trigger the analog trigger to count
176   */
177  public Counter(AnalogTrigger trigger) {
178    this();
179    if (trigger == null) {
180      throw new NullPointerException("The Analog Trigger given was null");
181    }
182    setUpSource(trigger.createOutput(AnalogTriggerType.kState));
183  }
184
185  @Override
186  public void free() {
187    setUpdateWhenEmpty(true);
188
189    clearUpSource();
190    clearDownSource();
191
192    CounterJNI.freeCounter(m_counter);
193
194    m_upSource = null;
195    m_downSource = null;
196    m_counter = 0;
197  }
198
199  /**
200   * The counter's FPGA index.
201   *
202   * @return the Counter's FPGA index
203   */
204  @SuppressWarnings("AbbreviationAsWordInName")
205  public int getFPGAIndex() {
206    return m_index;
207  }
208
209  /**
210   * Set the upsource for the counter as a digital input channel.
211   *
212   * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP
213   */
214  public void setUpSource(int channel) {
215    setUpSource(new DigitalInput(channel));
216    m_allocatedUpSource = true;
217  }
218
219  /**
220   * Set the source object that causes the counter to count up. Set the up counting DigitalSource.
221   *
222   * @param source the digital source to count
223   */
224  public void setUpSource(DigitalSource source) {
225    if (m_upSource != null && m_allocatedUpSource) {
226      m_upSource.free();
227      m_allocatedUpSource = false;
228    }
229    m_upSource = source;
230    CounterJNI.setCounterUpSource(m_counter, source.getPortHandleForRouting(),
231        source.getAnalogTriggerTypeForRouting());
232  }
233
234  /**
235   * Set the up counting source to be an analog trigger.
236   *
237   * @param analogTrigger The analog trigger object that is used for the Up Source
238   * @param triggerType   The analog trigger output that will trigger the counter.
239   */
240  public void setUpSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) {
241    if (analogTrigger == null) {
242      throw new NullPointerException("Analog Trigger given was null");
243    }
244    if (triggerType == null) {
245      throw new NullPointerException("Analog Trigger Type given was null");
246    }
247    setUpSource(analogTrigger.createOutput(triggerType));
248    m_allocatedUpSource = true;
249  }
250
251  /**
252   * Set the edge sensitivity on an up counting source. Set the up source to either detect rising
253   * edges or falling edges.
254   *
255   * @param risingEdge  true to count rising edge
256   * @param fallingEdge true to count falling edge
257   */
258  public void setUpSourceEdge(boolean risingEdge, boolean fallingEdge) {
259    if (m_upSource == null) {
260      throw new RuntimeException("Up Source must be set before setting the edge!");
261    }
262    CounterJNI.setCounterUpSourceEdge(m_counter, risingEdge, fallingEdge);
263  }
264
265  /**
266   * Disable the up counting source to the counter.
267   */
268  public void clearUpSource() {
269    if (m_upSource != null && m_allocatedUpSource) {
270      m_upSource.free();
271      m_allocatedUpSource = false;
272    }
273    m_upSource = null;
274
275    CounterJNI.clearCounterUpSource(m_counter);
276  }
277
278  /**
279   * Set the down counting source to be a digital input channel.
280   *
281   * @param channel the DIO channel to count 0-9 are on-board, 10-25 are on the MXP
282   */
283  public void setDownSource(int channel) {
284    setDownSource(new DigitalInput(channel));
285    m_allocatedDownSource = true;
286  }
287
288  /**
289   * Set the source object that causes the counter to count down. Set the down counting
290   * DigitalSource.
291   *
292   * @param source the digital source to count
293   */
294  public void setDownSource(DigitalSource source) {
295    if (source == null) {
296      throw new NullPointerException("The Digital Source given was null");
297    }
298
299    if (m_downSource != null && m_allocatedDownSource) {
300      m_downSource.free();
301      m_allocatedDownSource = false;
302    }
303    CounterJNI.setCounterDownSource(m_counter, source.getPortHandleForRouting(),
304        source.getAnalogTriggerTypeForRouting());
305    m_downSource = source;
306  }
307
308  /**
309   * Set the down counting source to be an analog trigger.
310   *
311   * @param analogTrigger The analog trigger object that is used for the Down Source
312   * @param triggerType   The analog trigger output that will trigger the counter.
313   */
314  public void setDownSource(AnalogTrigger analogTrigger, AnalogTriggerType triggerType) {
315    if (analogTrigger == null) {
316      throw new NullPointerException("Analog Trigger given was null");
317    }
318    if (triggerType == null) {
319      throw new NullPointerException("Analog Trigger Type given was null");
320    }
321
322    setDownSource(analogTrigger.createOutput(triggerType));
323    m_allocatedDownSource = true;
324  }
325
326  /**
327   * Set the edge sensitivity on a down counting source. Set the down source to either detect rising
328   * edges or falling edges.
329   *
330   * @param risingEdge  true to count the rising edge
331   * @param fallingEdge true to count the falling edge
332   */
333  public void setDownSourceEdge(boolean risingEdge, boolean fallingEdge) {
334    if (m_downSource == null) {
335      throw new RuntimeException(" Down Source must be set before setting the edge!");
336    }
337    CounterJNI.setCounterDownSourceEdge(m_counter, risingEdge, fallingEdge);
338  }
339
340  /**
341   * Disable the down counting source to the counter.
342   */
343  public void clearDownSource() {
344    if (m_downSource != null && m_allocatedDownSource) {
345      m_downSource.free();
346      m_allocatedDownSource = false;
347    }
348    m_downSource = null;
349
350    CounterJNI.clearCounterDownSource(m_counter);
351  }
352
353  /**
354   * Set standard up / down counting mode on this counter. Up and down counts are sourced
355   * independently from two inputs.
356   */
357  public void setUpDownCounterMode() {
358    CounterJNI.setCounterUpDownMode(m_counter);
359  }
360
361  /**
362   * Set external direction mode on this counter. Counts are sourced on the Up counter input. The
363   * Down counter input represents the direction to count.
364   */
365  public void setExternalDirectionMode() {
366    CounterJNI.setCounterExternalDirectionMode(m_counter);
367  }
368
369  /**
370   * Set Semi-period mode on this counter. Counts up on both rising and falling edges.
371   *
372   * @param highSemiPeriod true to count up on both rising and falling
373   */
374  public void setSemiPeriodMode(boolean highSemiPeriod) {
375    CounterJNI.setCounterSemiPeriodMode(m_counter, highSemiPeriod);
376  }
377
378  /**
379   * Configure the counter to count in up or down based on the length of the input pulse. This mode
380   * is most useful for direction sensitive gear tooth sensors.
381   *
382   * @param threshold The pulse length beyond which the counter counts the opposite direction. Units
383   *                  are seconds.
384   */
385  public void setPulseLengthMode(double threshold) {
386    CounterJNI.setCounterPulseLengthMode(m_counter, threshold);
387  }
388
389  /**
390   * Read the current counter value. Read the value at this instant. It may still be running, so it
391   * reflects the current value. Next time it is read, it might have a different value.
392   */
393  @Override
394  public int get() {
395    return CounterJNI.getCounter(m_counter);
396  }
397
398  /**
399   * Read the current scaled counter value. Read the value at this instant, scaled by the distance
400   * per pulse (defaults to 1).
401   *
402   * @return The distance since the last reset
403   */
404  public double getDistance() {
405    return get() * m_distancePerPulse;
406  }
407
408  /**
409   * Reset the Counter to zero. Set the counter value to zero. This doesn't effect the running state
410   * of the counter, just sets the current value to zero.
411   */
412  @Override
413  public void reset() {
414    CounterJNI.resetCounter(m_counter);
415  }
416
417  /**
418   * Set the maximum period where the device is still considered "moving". Sets the maximum period
419   * where the device is considered moving. This value is used to determine the "stopped" state of
420   * the counter using the GetStopped method.
421   *
422   * @param maxPeriod The maximum period where the counted device is considered moving in seconds.
423   */
424  @Override
425  public void setMaxPeriod(double maxPeriod) {
426    CounterJNI.setCounterMaxPeriod(m_counter, maxPeriod);
427  }
428
429  /**
430   * Select whether you want to continue updating the event timer output when there are no samples
431   * captured. The output of the event timer has a buffer of periods that are averaged and posted to
432   * a register on the FPGA. When the timer detects that the event source has stopped (based on the
433   * MaxPeriod) the buffer of samples to be averaged is emptied. If you enable the update when
434   * empty, you will be notified of the stopped source and the event time will report 0 samples. If
435   * you disable update when empty, the most recent average will remain on the output until a new
436   * sample is acquired. You will never see 0 samples output (except when there have been no events
437   * since an FPGA reset) and you will likely not see the stopped bit become true (since it is
438   * updated at the end of an average and there are no samples to average).
439   *
440   * @param enabled true to continue updating
441   */
442  public void setUpdateWhenEmpty(boolean enabled) {
443    CounterJNI.setCounterUpdateWhenEmpty(m_counter, enabled);
444  }
445
446  /**
447   * Determine if the clock is stopped. Determine if the clocked input is stopped based on the
448   * MaxPeriod value set using the SetMaxPeriod method. If the clock exceeds the MaxPeriod, then the
449   * device (and counter) are assumed to be stopped and it returns true.
450   *
451   * @return true if the most recent counter period exceeds the MaxPeriod value set by SetMaxPeriod.
452   */
453  @Override
454  public boolean getStopped() {
455    return CounterJNI.getCounterStopped(m_counter);
456  }
457
458  /**
459   * The last direction the counter value changed.
460   *
461   * @return The last direction the counter value changed.
462   */
463  @Override
464  public boolean getDirection() {
465    return CounterJNI.getCounterDirection(m_counter);
466  }
467
468  /**
469   * Set the Counter to return reversed sensing on the direction. This allows counters to change the
470   * direction they are counting in the case of 1X and 2X quadrature encoding only. Any other
471   * counter mode isn't supported.
472   *
473   * @param reverseDirection true if the value counted should be negated.
474   */
475  public void setReverseDirection(boolean reverseDirection) {
476    CounterJNI.setCounterReverseDirection(m_counter, reverseDirection);
477  }
478
479  /**
480   * Get the Period of the most recent count. Returns the time interval of the most recent count.
481   * This can be used for velocity calculations to determine shaft speed.
482   *
483   * @return The period of the last two pulses in units of seconds.
484   */
485  @Override
486  public double getPeriod() {
487    return CounterJNI.getCounterPeriod(m_counter);
488  }
489
490  /**
491   * Get the current rate of the Counter. Read the current rate of the counter accounting for the
492   * distance per pulse value. The default value for distance per pulse (1) yields units of pulses
493   * per second.
494   *
495   * @return The rate in units/sec
496   */
497  public double getRate() {
498    return m_distancePerPulse / getPeriod();
499  }
500
501  /**
502   * Set the Samples to Average which specifies the number of samples of the timer to average when
503   * calculating the period. Perform averaging to account for mechanical imperfections or as
504   * oversampling to increase resolution.
505   *
506   * @param samplesToAverage The number of samples to average from 1 to 127.
507   */
508  public void setSamplesToAverage(int samplesToAverage) {
509    CounterJNI.setCounterSamplesToAverage(m_counter, samplesToAverage);
510  }
511
512  /**
513   * Get the Samples to Average which specifies the number of samples of the timer to average when
514   * calculating the period. Perform averaging to account for mechanical imperfections or as
515   * oversampling to increase resolution.
516   *
517   * @return SamplesToAverage The number of samples being averaged (from 1 to 127)
518   */
519  public int getSamplesToAverage() {
520    return CounterJNI.getCounterSamplesToAverage(m_counter);
521  }
522
523  /**
524   * Set the distance per pulse for this counter. This sets the multiplier used to determine the
525   * distance driven based on the count value from the encoder. Set this value based on the Pulses
526   * per Revolution and factor in any gearing reductions. This distance can be in any units you
527   * like, linear or angular.
528   *
529   * @param distancePerPulse The scale factor that will be used to convert pulses to useful units.
530   */
531  public void setDistancePerPulse(double distancePerPulse) {
532    m_distancePerPulse = distancePerPulse;
533  }
534
535  /**
536   * Set which parameter of the encoder you are using as a process control variable. The counter
537   * class supports the rate and distance parameters.
538   *
539   * @param pidSource An enum to select the parameter.
540   */
541  public void setPIDSourceType(PIDSourceType pidSource) {
542    if (pidSource == null) {
543      throw new NullPointerException("PID Source Parameter given was null");
544    }
545    BoundaryException.assertWithinBounds(pidSource.value, 0, 1);
546    m_pidSource = pidSource;
547  }
548
549  public PIDSourceType getPIDSourceType() {
550    return m_pidSource;
551  }
552
553  @Override
554  public double pidGet() {
555    switch (m_pidSource) {
556      case kDisplacement:
557        return getDistance();
558      case kRate:
559        return getRate();
560      default:
561        return 0.0;
562    }
563  }
564
565  @Override
566  public String getSmartDashboardType() {
567    return "Counter";
568  }
569
570  private ITable m_table;
571
572  @Override
573  public void initTable(ITable subtable) {
574    m_table = subtable;
575    updateTable();
576  }
577
578  @Override
579  public ITable getTable() {
580    return m_table;
581  }
582
583  @Override
584  public void updateTable() {
585    if (m_table != null) {
586      m_table.putNumber("Value", get());
587    }
588  }
589
590  @Override
591  public void startLiveWindowMode() {
592  }
593
594  @Override
595  public void stopLiveWindowMode() {
596  }
597}