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}