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;
006
007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.hal.EncoderJNI;
010import edu.wpi.first.hal.HAL;
011import edu.wpi.first.hal.SimDevice;
012import edu.wpi.first.util.sendable.Sendable;
013import edu.wpi.first.util.sendable.SendableBuilder;
014import edu.wpi.first.util.sendable.SendableRegistry;
015
016/**
017 * Class to read quadrature encoders.
018 *
019 * <p>Quadrature encoders are devices that count shaft rotation and can sense direction. The output
020 * of the Encoder class is an integer that can count either up or down, and can go negative for
021 * reverse direction counting. When creating Encoders, a direction can be supplied that inverts the
022 * sense of the output to make code more readable if the encoder is mounted such that forward
023 * movement generates negative values. Quadrature encoders have two digital outputs, an A Channel
024 * and a B Channel, that are out of phase with each other for direction sensing.
025 *
026 * <p>All encoders will immediately start counting - reset() them if you need them to be zeroed
027 * before use.
028 */
029public class Encoder implements CounterBase, Sendable, AutoCloseable {
030  private final EncodingType m_encodingType;
031
032  int m_encoder; // the HAL encoder object
033
034  /**
035   * Common initialization code for Encoders. This code allocates resources for Encoders and is
036   * common to all constructors.
037   *
038   * <p>The encoder will start counting immediately.
039   *
040   * @param aChannel The a channel.
041   * @param bChannel The b channel.
042   * @param reverseDirection If true, counts down instead of up (this is all relative)
043   */
044  private void initEncoder(
045      int aChannel, int bChannel, boolean reverseDirection, final EncodingType type) {
046    m_encoder = EncoderJNI.initializeEncoder(aChannel, bChannel, reverseDirection, type.value);
047
048    String typeStr =
049        switch (type) {
050          case k1X -> "Encoder:1x";
051          case k2X -> "Encoder:2x";
052          case k4X -> "Encoder:4x";
053          default -> "Encoder";
054        };
055    HAL.reportUsage("IO[" + aChannel + "," + bChannel + "]", typeStr);
056
057    int fpgaIndex = getFPGAIndex();
058    SendableRegistry.add(this, "Encoder", fpgaIndex);
059  }
060
061  /**
062   * Encoder constructor. Construct a Encoder given a and b channels.
063   *
064   * <p>The encoder will start counting immediately.
065   *
066   * @param channelA The 'a' channel DIO channel. 0-9 are on-board, 10-25 are on the MXP port
067   * @param channelB The 'b' channel DIO channel. 0-9 are on-board, 10-25 are on the MXP port
068   * @param reverseDirection represents the orientation of the encoder and inverts the output values
069   *     if necessary so forward represents positive values.
070   */
071  public Encoder(final int channelA, final int channelB, boolean reverseDirection) {
072    this(channelA, channelB, reverseDirection, EncodingType.k4X);
073  }
074
075  /**
076   * Encoder constructor. Construct an Encoder given a and b channels.
077   *
078   * <p>The encoder will start counting immediately.
079   *
080   * @param channelA The a channel digital input channel.
081   * @param channelB The b channel digital input channel.
082   */
083  public Encoder(final int channelA, final int channelB) {
084    this(channelA, channelB, false);
085  }
086
087  /**
088   * Encoder constructor. Construct an Encoder given a and b channels.
089   *
090   * <p>The encoder will start counting immediately.
091   *
092   * @param channelA The a channel digital input channel.
093   * @param channelB The b channel digital input channel.
094   * @param reverseDirection represents the orientation of the encoder and inverts the output values
095   *     if necessary so forward represents positive values.
096   * @param encodingType either k1X, k2X, or k4X to indicate 1X, 2X or 4X decoding. If 4X is
097   *     selected, then an encoder FPGA object is used and the returned counts will be 4x the
098   *     encoder spec'd value since all rising and falling edges are counted. If 1X or 2X are
099   *     selected, then a counter object will be used and the returned value will either exactly
100   *     match the spec'd count or be double (2x) the spec'd count.
101   */
102  @SuppressWarnings("this-escape")
103  public Encoder(
104      final int channelA,
105      final int channelB,
106      boolean reverseDirection,
107      final EncodingType encodingType) {
108    requireNonNullParam(encodingType, "encodingType", "Encoder");
109
110    m_encodingType = encodingType;
111    // SendableRegistry.addChild(this, m_aSource);
112    // SendableRegistry.addChild(this, m_bSource);
113    initEncoder(channelA, channelB, reverseDirection, encodingType);
114  }
115
116  /**
117   * Get the FPGA index of the encoder.
118   *
119   * @return The Encoder's FPGA index.
120   */
121  public int getFPGAIndex() {
122    return EncoderJNI.getEncoderFPGAIndex(m_encoder);
123  }
124
125  /**
126   * Used to divide raw edge counts down to spec'd counts.
127   *
128   * @return The encoding scale factor 1x, 2x, or 4x, per the requested encoding type.
129   */
130  public int getEncodingScale() {
131    return EncoderJNI.getEncoderEncodingScale(m_encoder);
132  }
133
134  @Override
135  public void close() {
136    SendableRegistry.remove(this);
137    // if (m_aSource != null && m_allocatedA) {
138    //   m_aSource.close();
139    //   m_allocatedA = false;
140    // }
141    // if (m_bSource != null && m_allocatedB) {
142    //   m_bSource.close();
143    //   m_allocatedB = false;
144    // }
145    // if (m_indexSource != null && m_allocatedI) {
146    //   m_indexSource.close();
147    //   m_allocatedI = false;
148    // }
149
150    // m_aSource = null;
151    // m_bSource = null;
152    // m_indexSource = null;
153    EncoderJNI.freeEncoder(m_encoder);
154    m_encoder = 0;
155  }
156
157  /**
158   * Gets the raw value from the encoder. The raw value is the actual count unscaled by the 1x, 2x,
159   * or 4x scale factor.
160   *
161   * @return Current raw count from the encoder
162   */
163  public int getRaw() {
164    return EncoderJNI.getEncoderRaw(m_encoder);
165  }
166
167  /**
168   * Gets the current count. Returns the current count on the Encoder. This method compensates for
169   * the decoding type.
170   *
171   * @return Current count from the Encoder adjusted for the 1x, 2x, or 4x scale factor.
172   */
173  @Override
174  public int get() {
175    return EncoderJNI.getEncoder(m_encoder);
176  }
177
178  /** Reset the Encoder distance to zero. Resets the current count to zero on the encoder. */
179  @Override
180  public void reset() {
181    EncoderJNI.resetEncoder(m_encoder);
182  }
183
184  /**
185   * Returns the period of the most recent pulse. Returns the period of the most recent Encoder
186   * pulse in seconds. This method compensates for the decoding type.
187   *
188   * <p><b>Warning:</b> This returns unscaled periods. Use getRate() for rates that are scaled using
189   * the value from setDistancePerPulse().
190   *
191   * @return Period in seconds of the most recent pulse.
192   * @deprecated Use getRate() in favor of this method.
193   */
194  @Override
195  @Deprecated
196  public double getPeriod() {
197    return EncoderJNI.getEncoderPeriod(m_encoder);
198  }
199
200  /**
201   * Sets the maximum period for stopped detection. Sets the value that represents the maximum
202   * period of the Encoder before it will assume that the attached device is stopped. This timeout
203   * allows users to determine if the wheels or other shaft has stopped rotating. This method
204   * compensates for the decoding type.
205   *
206   * @param maxPeriod The maximum time between rising and falling edges before the FPGA will report
207   *     the device stopped. This is expressed in seconds.
208   * @deprecated Use setMinRate() in favor of this method. This takes unscaled periods and
209   *     setMinRate() scales using value from setDistancePerPulse().
210   */
211  @Override
212  @Deprecated
213  public void setMaxPeriod(double maxPeriod) {
214    EncoderJNI.setEncoderMaxPeriod(m_encoder, maxPeriod);
215  }
216
217  /**
218   * Determine if the encoder is stopped. Using the MaxPeriod value, a boolean is returned that is
219   * true if the encoder is considered stopped and false if it is still moving. A stopped encoder is
220   * one where the most recent pulse width exceeds the MaxPeriod.
221   *
222   * @return True if the encoder is considered stopped.
223   */
224  @Override
225  public boolean getStopped() {
226    return EncoderJNI.getEncoderStopped(m_encoder);
227  }
228
229  /**
230   * The last direction the encoder value changed.
231   *
232   * @return The last direction the encoder value changed.
233   */
234  @Override
235  public boolean getDirection() {
236    return EncoderJNI.getEncoderDirection(m_encoder);
237  }
238
239  /**
240   * Get the distance the robot has driven since the last reset as scaled by the value from {@link
241   * #setDistancePerPulse(double)}.
242   *
243   * @return The distance driven since the last reset
244   */
245  public double getDistance() {
246    return EncoderJNI.getEncoderDistance(m_encoder);
247  }
248
249  /**
250   * Get the current rate of the encoder. Units are distance per second as scaled by the value from
251   * setDistancePerPulse().
252   *
253   * @return The current rate of the encoder.
254   */
255  public double getRate() {
256    return EncoderJNI.getEncoderRate(m_encoder);
257  }
258
259  /**
260   * Set the minimum rate of the device before the hardware reports it stopped.
261   *
262   * @param minRate The minimum rate. The units are in distance per second as scaled by the value
263   *     from setDistancePerPulse().
264   */
265  public void setMinRate(double minRate) {
266    EncoderJNI.setEncoderMinRate(m_encoder, minRate);
267  }
268
269  /**
270   * Set the distance per pulse for this encoder. This sets the multiplier used to determine the
271   * distance driven based on the count value from the encoder. Do not include the decoding type in
272   * this scale. The library already compensates for the decoding type. Set this value based on the
273   * encoder's rated Pulses per Revolution and factor in gearing reductions following the encoder
274   * shaft. This distance can be in any units you like, linear or angular.
275   *
276   * @param distancePerPulse The scale factor that will be used to convert pulses to useful units.
277   */
278  public void setDistancePerPulse(double distancePerPulse) {
279    EncoderJNI.setEncoderDistancePerPulse(m_encoder, distancePerPulse);
280  }
281
282  /**
283   * Get the distance per pulse for this encoder.
284   *
285   * @return The scale factor that will be used to convert pulses to useful units.
286   */
287  public double getDistancePerPulse() {
288    return EncoderJNI.getEncoderDistancePerPulse(m_encoder);
289  }
290
291  /**
292   * Set the direction sensing for this encoder. This sets the direction sensing on the encoder so
293   * that it could count in the correct software direction regardless of the mounting.
294   *
295   * @param reverseDirection true if the encoder direction should be reversed
296   */
297  public void setReverseDirection(boolean reverseDirection) {
298    EncoderJNI.setEncoderReverseDirection(m_encoder, reverseDirection);
299  }
300
301  /**
302   * Set the Samples to Average which specifies the number of samples of the timer to average when
303   * calculating the period. Perform averaging to account for mechanical imperfections or as
304   * oversampling to increase resolution.
305   *
306   * @param samplesToAverage The number of samples to average from 1 to 127.
307   */
308  public void setSamplesToAverage(int samplesToAverage) {
309    EncoderJNI.setEncoderSamplesToAverage(m_encoder, samplesToAverage);
310  }
311
312  /**
313   * Get the Samples to Average which specifies the number of samples of the timer to average when
314   * calculating the period. Perform averaging to account for mechanical imperfections or as
315   * oversampling to increase resolution.
316   *
317   * @return SamplesToAverage The number of samples being averaged (from 1 to 127)
318   */
319  public int getSamplesToAverage() {
320    return EncoderJNI.getEncoderSamplesToAverage(m_encoder);
321  }
322
323  /**
324   * Indicates this input is used by a simulated device.
325   *
326   * @param device simulated device handle
327   */
328  public void setSimDevice(SimDevice device) {
329    EncoderJNI.setEncoderSimDevice(m_encoder, device.getNativeHandle());
330  }
331
332  /**
333   * Gets the decoding scale factor for scaling raw values to full counts.
334   *
335   * @return decoding scale factor
336   */
337  public double getDecodingScaleFactor() {
338    return switch (m_encodingType) {
339      case k1X -> 1.0;
340      case k2X -> 0.5;
341      case k4X -> 0.25;
342    };
343  }
344
345  @Override
346  public void initSendable(SendableBuilder builder) {
347    if (EncoderJNI.getEncoderEncodingType(m_encoder) == EncodingType.k4X.value) {
348      builder.setSmartDashboardType("Quadrature Encoder");
349    } else {
350      builder.setSmartDashboardType("Encoder");
351    }
352
353    builder.addDoubleProperty("Speed", this::getRate, null);
354    builder.addDoubleProperty("Distance", this::getDistance, null);
355    builder.addDoubleProperty("Distance per Tick", this::getDistancePerPulse, null);
356  }
357}