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}