001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj.counter;
006
007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.hal.CounterJNI;
010import edu.wpi.first.hal.FRCNetComm.tResourceType;
011import edu.wpi.first.hal.HAL;
012import edu.wpi.first.util.sendable.Sendable;
013import edu.wpi.first.util.sendable.SendableBuilder;
014import edu.wpi.first.util.sendable.SendableRegistry;
015import edu.wpi.first.wpilibj.DigitalSource;
016import java.nio.ByteBuffer;
017import java.nio.ByteOrder;
018
019/**
020 * Tachometer.
021 *
022 * <p>The Tachometer class measures the time between digital pulses to determine the rotation speed
023 * of a mechanism. Examples of devices that could be used with the tachometer class are a hall
024 * effect sensor, break beam sensor, or optical sensor detecting tape on a shooter wheel. Unlike
025 * encoders, this class only needs a single digital input.
026 */
027public class Tachometer implements Sendable, AutoCloseable {
028  private final DigitalSource m_source;
029  private final int m_handle;
030  private int m_edgesPerRevolution = 1;
031
032  /**
033   * Constructs a new tachometer.
034   *
035   * @param source The DigitalSource (e.g. DigitalInput) of the Tachometer.
036   */
037  @SuppressWarnings("this-escape")
038  public Tachometer(DigitalSource source) {
039    m_source = requireNonNullParam(source, "source", "Tachometer");
040
041    ByteBuffer index = ByteBuffer.allocateDirect(4);
042    // set the byte order
043    index.order(ByteOrder.LITTLE_ENDIAN);
044    m_handle = CounterJNI.initializeCounter(CounterJNI.TWO_PULSE, index.asIntBuffer());
045
046    CounterJNI.setCounterUpSource(
047        m_handle, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting());
048    CounterJNI.setCounterUpSourceEdge(m_handle, true, false);
049
050    int intIndex = index.getInt();
051    HAL.report(tResourceType.kResourceType_Counter, intIndex + 1);
052    SendableRegistry.addLW(this, "Tachometer", intIndex);
053  }
054
055  @Override
056  public void close() {
057    SendableRegistry.remove(this);
058    CounterJNI.freeCounter(m_handle);
059    CounterJNI.suppressUnused(m_source);
060  }
061
062  /**
063   * Gets the tachometer period.
064   *
065   * @return Current period (in seconds).
066   */
067  public double getPeriod() {
068    return CounterJNI.getCounterPeriod(m_handle);
069  }
070
071  /**
072   * Gets the tachometer frequency.
073   *
074   * @return Current frequency (in hertz).
075   */
076  public double getFrequency() {
077    double period = getPeriod();
078    if (period == 0) {
079      return 0;
080    }
081    return 1 / period;
082  }
083
084  /**
085   * Gets the number of edges per revolution.
086   *
087   * @return Edges per revolution.
088   */
089  public int getEdgesPerRevolution() {
090    return m_edgesPerRevolution;
091  }
092
093  /**
094   * Sets the number of edges per revolution.
095   *
096   * @param edgesPerRevolution Edges per revolution.
097   */
098  public void setEdgesPerRevolution(int edgesPerRevolution) {
099    m_edgesPerRevolution = edgesPerRevolution;
100  }
101
102  /**
103   * Gets the current tachometer revolutions per second.
104   *
105   * <p>setEdgesPerRevolution must be set with a non 0 value for this to return valid values.
106   *
107   * @return Current RPS.
108   */
109  public double getRevolutionsPerSecond() {
110    double period = getPeriod();
111    if (period == 0) {
112      return 0;
113    }
114    int edgesPerRevolution = getEdgesPerRevolution();
115    if (edgesPerRevolution == 0) {
116      return 0;
117    }
118    return (1.0 / edgesPerRevolution) / period;
119  }
120
121  /**
122   * Gets the current tachometer revolutions per minute.
123   *
124   * <p>setEdgesPerRevolution must be set with a non 0 value for this to return valid values.
125   *
126   * @return Current RPM.
127   */
128  public double getRevolutionsPerMinute() {
129    return getRevolutionsPerSecond() * 60;
130  }
131
132  /**
133   * Gets if the tachometer is stopped.
134   *
135   * @return True if the tachometer is stopped.
136   */
137  public boolean getStopped() {
138    return CounterJNI.getCounterStopped(m_handle);
139  }
140
141  /**
142   * Gets the number of samples to average.
143   *
144   * @return Samples to average.
145   */
146  public int getSamplesToAverage() {
147    return CounterJNI.getCounterSamplesToAverage(m_handle);
148  }
149
150  /**
151   * Sets the number of samples to average.
152   *
153   * @param samplesToAverage Samples to average.
154   */
155  public void setSamplesToAverage(int samplesToAverage) {
156    CounterJNI.setCounterSamplesToAverage(m_handle, samplesToAverage);
157  }
158
159  /**
160   * Sets the maximum period before the tachometer is considered stopped.
161   *
162   * @param maxPeriod The max period (in seconds).
163   */
164  public void setMaxPeriod(double maxPeriod) {
165    CounterJNI.setCounterMaxPeriod(m_handle, maxPeriod);
166  }
167
168  /**
169   * Sets if to update when empty.
170   *
171   * @param updateWhenEmpty Update when empty if true.
172   */
173  public void setUpdateWhenEmpty(boolean updateWhenEmpty) {
174    CounterJNI.setCounterUpdateWhenEmpty(m_handle, updateWhenEmpty);
175  }
176
177  @Override
178  public void initSendable(SendableBuilder builder) {
179    builder.setSmartDashboardType("Tachometer");
180    builder.addDoubleProperty("RPS", this::getRevolutionsPerSecond, null);
181    builder.addDoubleProperty("RPM", this::getRevolutionsPerMinute, null);
182  }
183}