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  public Tachometer(DigitalSource source) {
038    m_source = requireNonNullParam(source, "source", "Tachometer");
039
040    ByteBuffer index = ByteBuffer.allocateDirect(4);
041    // set the byte order
042    index.order(ByteOrder.LITTLE_ENDIAN);
043    m_handle = CounterJNI.initializeCounter(CounterJNI.TWO_PULSE, index.asIntBuffer());
044
045    CounterJNI.setCounterUpSource(
046        m_handle, source.getPortHandleForRouting(), source.getAnalogTriggerTypeForRouting());
047    CounterJNI.setCounterUpSourceEdge(m_handle, true, false);
048
049    int intIndex = index.getInt();
050    HAL.report(tResourceType.kResourceType_Counter, intIndex + 1);
051    SendableRegistry.addLW(this, "Tachometer", intIndex);
052  }
053
054  @Override
055  public void close() {
056    SendableRegistry.remove(this);
057    CounterJNI.freeCounter(m_handle);
058    CounterJNI.suppressUnused(m_source);
059  }
060
061  /**
062   * Gets the tachometer period.
063   *
064   * @return Current period (in seconds).
065   */
066  public double getPeriod() {
067    return CounterJNI.getCounterPeriod(m_handle);
068  }
069
070  /**
071   * Gets the tachometer frequency.
072   *
073   * @return Current frequency (in hertz).
074   */
075  public double getFrequency() {
076    double period = getPeriod();
077    if (period == 0) {
078      return 0;
079    }
080    return 1 / period;
081  }
082
083  /**
084   * Gets the number of edges per revolution.
085   *
086   * @return Edges per revolution.
087   */
088  public int getEdgesPerRevolution() {
089    return m_edgesPerRevolution;
090  }
091
092  /**
093   * Sets the number of edges per revolution.
094   *
095   * @param edgesPerRevolution Edges per revolution.
096   */
097  public void setEdgesPerRevolution(int edgesPerRevolution) {
098    m_edgesPerRevolution = edgesPerRevolution;
099  }
100
101  /**
102   * Gets the current tachometer revolutions per second.
103   *
104   * <p>setEdgesPerRevolution must be set with a non 0 value for this to return valid values.
105   *
106   * @return Current RPS.
107   */
108  public double getRevolutionsPerSecond() {
109    double period = getPeriod();
110    if (period == 0) {
111      return 0;
112    }
113    int edgesPerRevolution = getEdgesPerRevolution();
114    if (edgesPerRevolution == 0) {
115      return 0;
116    }
117    return (1.0 / edgesPerRevolution) / period;
118  }
119
120  /**
121   * Gets the current tachometer revolutions per minute.
122   *
123   * <p>setEdgesPerRevolution must be set with a non 0 value for this to return valid values.
124   *
125   * @return Current RPM.
126   */
127  public double getRevolutionsPerMinute() {
128    return getRevolutionsPerSecond() * 60;
129  }
130
131  /**
132   * Gets if the tachometer is stopped.
133   *
134   * @return True if the tachometer is stopped.
135   */
136  public boolean getStopped() {
137    return CounterJNI.getCounterStopped(m_handle);
138  }
139
140  /**
141   * Gets the number of samples to average.
142   *
143   * @return Samples to average.
144   */
145  public int getSamplesToAverage() {
146    return CounterJNI.getCounterSamplesToAverage(m_handle);
147  }
148
149  /**
150   * Sets the number of samples to average.
151   *
152   * @param samplesToAverage Samples to average.
153   */
154  public void setSamplesToAverage(int samplesToAverage) {
155    CounterJNI.setCounterSamplesToAverage(m_handle, samplesToAverage);
156  }
157
158  /**
159   * Sets the maximum period before the tachometer is considered stopped.
160   *
161   * @param maxPeriod The max period (in seconds).
162   */
163  public void setMaxPeriod(double maxPeriod) {
164    CounterJNI.setCounterMaxPeriod(m_handle, maxPeriod);
165  }
166
167  /**
168   * Sets if to update when empty.
169   *
170   * @param updateWhenEmpty Update when empty if true.
171   */
172  public void setUpdateWhenEmpty(boolean updateWhenEmpty) {
173    CounterJNI.setCounterUpdateWhenEmpty(m_handle, updateWhenEmpty);
174  }
175
176  @Override
177  public void initSendable(SendableBuilder builder) {
178    builder.setSmartDashboardType("Tachometer");
179    builder.addDoubleProperty("RPS", this::getRevolutionsPerSecond, null);
180    builder.addDoubleProperty("RPM", this::getRevolutionsPerMinute, null);
181  }
182}