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 edu.wpi.first.hal.DriverStationJNI;
008import edu.wpi.first.math.MathUtil;
009import edu.wpi.first.math.Pair;
010import edu.wpi.first.wpilibj.event.BooleanEvent;
011import edu.wpi.first.wpilibj.event.EventLoop;
012import java.util.HashMap;
013import java.util.Map;
014
015/**
016 * Handle input from standard HID devices connected to the Driver Station.
017 *
018 * <p>This class handles standard input that comes from the Driver Station. Each time a value is
019 * requested the most recent value is returned. There is a single class instance for each device and
020 * the mapping of ports to hardware buttons depends on the code in the Driver Station.
021 */
022public class GenericHID {
023  /** Represents a rumble output on the Joystick. */
024  public enum RumbleType {
025    /** Left rumble motor. */
026    kLeftRumble,
027    /** Right rumble motor. */
028    kRightRumble,
029    /** Both left and right rumble motors. */
030    kBothRumble
031  }
032
033  /** USB HID interface type. */
034  public enum HIDType {
035    /** Unknown. */
036    kUnknown(-1),
037    /** XInputUnknown. */
038    kXInputUnknown(0),
039    /** XInputGamepad. */
040    kXInputGamepad(1),
041    /** XInputWheel. */
042    kXInputWheel(2),
043    /** XInputArcadeStick. */
044    kXInputArcadeStick(3),
045    /** XInputFlightStick. */
046    kXInputFlightStick(4),
047    /** XInputDancePad. */
048    kXInputDancePad(5),
049    /** XInputGuitar. */
050    kXInputGuitar(6),
051    /** XInputGuitar2. */
052    kXInputGuitar2(7),
053    /** XInputDrumKit. */
054    kXInputDrumKit(8),
055    /** XInputGuitar3. */
056    kXInputGuitar3(11),
057    /** XInputArcadePad. */
058    kXInputArcadePad(19),
059    /** HIDJoystick. */
060    kHIDJoystick(20),
061    /** HIDGamepad. */
062    kHIDGamepad(21),
063    /** HIDDriving. */
064    kHIDDriving(22),
065    /** HIDFlight. */
066    kHIDFlight(23),
067    /** HID1stPerson. */
068    kHID1stPerson(24);
069
070    /** HIDType value. */
071    public final int value;
072
073    private static final Map<Integer, HIDType> map = new HashMap<>();
074
075    HIDType(int value) {
076      this.value = value;
077    }
078
079    static {
080      for (HIDType hidType : HIDType.values()) {
081        map.put(hidType.value, hidType);
082      }
083    }
084
085    /**
086     * Creates an HIDType with the given value.
087     *
088     * @param value HIDType's value.
089     * @return HIDType with the given value.
090     */
091    public static HIDType of(int value) {
092      return map.get(value);
093    }
094  }
095
096  private final int m_port;
097  private int m_outputs;
098  private int m_leftRumble;
099  private int m_rightRumble;
100  private final Map<EventLoop, Map<Integer, BooleanEvent>> m_buttonCache = new HashMap<>();
101  private final Map<EventLoop, Map<Pair<Integer, Double>, BooleanEvent>> m_axisLessThanCache =
102      new HashMap<>();
103  private final Map<EventLoop, Map<Pair<Integer, Double>, BooleanEvent>> m_axisGreaterThanCache =
104      new HashMap<>();
105  private final Map<EventLoop, Map<Integer, BooleanEvent>> m_povCache = new HashMap<>();
106
107  /**
108   * Construct an instance of a device.
109   *
110   * @param port The port index on the Driver Station that the device is plugged into.
111   */
112  public GenericHID(int port) {
113    m_port = port;
114  }
115
116  /**
117   * Get the button value (starting at button 1).
118   *
119   * <p>The buttons are returned in a single 16 bit value with one bit representing the state of
120   * each button. The appropriate button is returned as a boolean value.
121   *
122   * <p>This method returns true if the button is being held down at the time that this method is
123   * being called.
124   *
125   * @param button The button number to be read (starting at 1)
126   * @return The state of the button.
127   */
128  public boolean getRawButton(int button) {
129    return DriverStation.getStickButton(m_port, (byte) button);
130  }
131
132  /**
133   * Whether the button was pressed since the last check. Button indexes begin at 1.
134   *
135   * <p>This method returns true if the button went from not pressed to held down since the last
136   * time this method was called. This is useful if you only want to call a function once when you
137   * press the button.
138   *
139   * @param button The button index, beginning at 1.
140   * @return Whether the button was pressed since the last check.
141   */
142  public boolean getRawButtonPressed(int button) {
143    return DriverStation.getStickButtonPressed(m_port, (byte) button);
144  }
145
146  /**
147   * Whether the button was released since the last check. Button indexes begin at 1.
148   *
149   * <p>This method returns true if the button went from held down to not pressed since the last
150   * time this method was called. This is useful if you only want to call a function once when you
151   * release the button.
152   *
153   * @param button The button index, beginning at 1.
154   * @return Whether the button was released since the last check.
155   */
156  public boolean getRawButtonReleased(int button) {
157    return DriverStation.getStickButtonReleased(m_port, button);
158  }
159
160  /**
161   * Constructs an event instance around this button's digital signal.
162   *
163   * @param button the button index
164   * @param loop the event loop instance to attach the event to.
165   * @return an event instance representing the button's digital signal attached to the given loop.
166   */
167  public BooleanEvent button(int button, EventLoop loop) {
168    synchronized (m_buttonCache) {
169      var cache = m_buttonCache.computeIfAbsent(loop, k -> new HashMap<>());
170      return cache.computeIfAbsent(button, k -> new BooleanEvent(loop, () -> getRawButton(k)));
171    }
172  }
173
174  /**
175   * Get the value of the axis.
176   *
177   * @param axis The axis to read, starting at 0.
178   * @return The value of the axis.
179   */
180  public double getRawAxis(int axis) {
181    return DriverStation.getStickAxis(m_port, axis);
182  }
183
184  /**
185   * Get the angle in degrees of a POV on the HID.
186   *
187   * <p>The POV angles start at 0 in the up direction, and increase clockwise (e.g. right is 90,
188   * upper-left is 315).
189   *
190   * @param pov The index of the POV to read (starting at 0). Defaults to 0.
191   * @return the angle of the POV in degrees, or -1 if the POV is not pressed.
192   */
193  public int getPOV(int pov) {
194    return DriverStation.getStickPOV(m_port, pov);
195  }
196
197  /**
198   * Get the angle in degrees of the default POV (index 0) on the HID.
199   *
200   * <p>The POV angles start at 0 in the up direction, and increase clockwise (e.g. right is 90,
201   * upper-left is 315).
202   *
203   * @return the angle of the POV in degrees, or -1 if the POV is not pressed.
204   */
205  public int getPOV() {
206    return getPOV(0);
207  }
208
209  /**
210   * Constructs a BooleanEvent instance based around this angle of a POV on the HID.
211   *
212   * <p>The POV angles start at 0 in the up direction, and increase clockwise (eg right is 90,
213   * upper-left is 315).
214   *
215   * @param angle POV angle in degrees, or -1 for the center / not pressed.
216   * @param loop the event loop instance to attach the event to.
217   * @return a BooleanEvent instance based around this angle of a POV on the HID.
218   */
219  public BooleanEvent pov(int angle, EventLoop loop) {
220    return pov(0, angle, loop);
221  }
222
223  /**
224   * Constructs a BooleanEvent instance based around this angle of a POV on the HID.
225   *
226   * <p>The POV angles start at 0 in the up direction, and increase clockwise (e.g. right is 90,
227   * upper-left is 315).
228   *
229   * @param pov index of the POV to read (starting at 0). Defaults to 0.
230   * @param angle POV angle in degrees, or -1 for the center / not pressed.
231   * @param loop the event loop instance to attach the event to.
232   * @return a BooleanEvent instance based around this angle of a POV on the HID.
233   */
234  public BooleanEvent pov(int pov, int angle, EventLoop loop) {
235    synchronized (m_povCache) {
236      var cache = m_povCache.computeIfAbsent(loop, k -> new HashMap<>());
237      // angle can be -1, so use 3600 instead of 360
238      return cache.computeIfAbsent(
239          pov * 3600 + angle, k -> new BooleanEvent(loop, () -> getPOV(pov) == angle));
240    }
241  }
242
243  /**
244   * Constructs a BooleanEvent instance based around the 0 degree angle (up) of the default (index
245   * 0) POV on the HID.
246   *
247   * @param loop the event loop instance to attach the event to.
248   * @return a BooleanEvent instance based around the 0 degree angle of a POV on the HID.
249   */
250  public BooleanEvent povUp(EventLoop loop) {
251    return pov(0, loop);
252  }
253
254  /**
255   * Constructs a BooleanEvent instance based around the 45 degree angle (right up) of the default
256   * (index 0) POV on the HID.
257   *
258   * @param loop the event loop instance to attach the event to.
259   * @return a BooleanEvent instance based around the 45 degree angle of a POV on the HID.
260   */
261  public BooleanEvent povUpRight(EventLoop loop) {
262    return pov(45, loop);
263  }
264
265  /**
266   * Constructs a BooleanEvent instance based around the 90 degree angle (right) of the default
267   * (index 0) POV on the HID.
268   *
269   * @param loop the event loop instance to attach the event to.
270   * @return a BooleanEvent instance based around the 90 degree angle of a POV on the HID.
271   */
272  public BooleanEvent povRight(EventLoop loop) {
273    return pov(90, loop);
274  }
275
276  /**
277   * Constructs a BooleanEvent instance based around the 135 degree angle (right down) of the
278   * default (index 0) POV on the HID.
279   *
280   * @param loop the event loop instance to attach the event to.
281   * @return a BooleanEvent instance based around the 135 degree angle of a POV on the HID.
282   */
283  public BooleanEvent povDownRight(EventLoop loop) {
284    return pov(135, loop);
285  }
286
287  /**
288   * Constructs a BooleanEvent instance based around the 180 degree angle (down) of the default
289   * (index 0) POV on the HID.
290   *
291   * @param loop the event loop instance to attach the event to.
292   * @return a BooleanEvent instance based around the 180 degree angle of a POV on the HID.
293   */
294  public BooleanEvent povDown(EventLoop loop) {
295    return pov(180, loop);
296  }
297
298  /**
299   * Constructs a BooleanEvent instance based around the 225 degree angle (down left) of the default
300   * (index 0) POV on the HID.
301   *
302   * @param loop the event loop instance to attach the event to.
303   * @return a BooleanEvent instance based around the 225 degree angle of a POV on the HID.
304   */
305  public BooleanEvent povDownLeft(EventLoop loop) {
306    return pov(225, loop);
307  }
308
309  /**
310   * Constructs a BooleanEvent instance based around the 270 degree angle (left) of the default
311   * (index 0) POV on the HID.
312   *
313   * @param loop the event loop instance to attach the event to.
314   * @return a BooleanEvent instance based around the 270 degree angle of a POV on the HID.
315   */
316  public BooleanEvent povLeft(EventLoop loop) {
317    return pov(270, loop);
318  }
319
320  /**
321   * Constructs a BooleanEvent instance based around the 315 degree angle (left up) of the default
322   * (index 0) POV on the HID.
323   *
324   * @param loop the event loop instance to attach the event to.
325   * @return a BooleanEvent instance based around the 315 degree angle of a POV on the HID.
326   */
327  public BooleanEvent povUpLeft(EventLoop loop) {
328    return pov(315, loop);
329  }
330
331  /**
332   * Constructs a BooleanEvent instance based around the center (not pressed) of the default (index
333   * 0) POV on the HID.
334   *
335   * @param loop the event loop instance to attach the event to.
336   * @return a BooleanEvent instance based around the center of a POV on the HID.
337   */
338  public BooleanEvent povCenter(EventLoop loop) {
339    return pov(-1, loop);
340  }
341
342  /**
343   * Constructs an event instance that is true when the axis value is less than {@code threshold},
344   * attached to the given loop.
345   *
346   * @param axis The axis to read, starting at 0
347   * @param threshold The value below which this event should return true.
348   * @param loop the event loop instance to attach the event to.
349   * @return an event instance that is true when the axis value is less than the provided threshold.
350   */
351  public BooleanEvent axisLessThan(int axis, double threshold, EventLoop loop) {
352    synchronized (m_axisLessThanCache) {
353      var cache = m_axisLessThanCache.computeIfAbsent(loop, k -> new HashMap<>());
354      return cache.computeIfAbsent(
355          Pair.of(axis, threshold),
356          k -> new BooleanEvent(loop, () -> getRawAxis(axis) < threshold));
357    }
358  }
359
360  /**
361   * Constructs an event instance that is true when the axis value is greater than {@code
362   * threshold}, attached to the given loop.
363   *
364   * @param axis The axis to read, starting at 0
365   * @param threshold The value above which this event should return true.
366   * @param loop the event loop instance to attach the event to.
367   * @return an event instance that is true when the axis value is greater than the provided
368   *     threshold.
369   */
370  public BooleanEvent axisGreaterThan(int axis, double threshold, EventLoop loop) {
371    synchronized (m_axisGreaterThanCache) {
372      var cache = m_axisGreaterThanCache.computeIfAbsent(loop, k -> new HashMap<>());
373      return cache.computeIfAbsent(
374          Pair.of(axis, threshold),
375          k -> new BooleanEvent(loop, () -> getRawAxis(axis) > threshold));
376    }
377  }
378
379  /**
380   * Get the number of axes for the HID.
381   *
382   * @return the number of axis for the current HID
383   */
384  public int getAxisCount() {
385    return DriverStation.getStickAxisCount(m_port);
386  }
387
388  /**
389   * For the current HID, return the number of POVs.
390   *
391   * @return the number of POVs for the current HID
392   */
393  public int getPOVCount() {
394    return DriverStation.getStickPOVCount(m_port);
395  }
396
397  /**
398   * For the current HID, return the number of buttons.
399   *
400   * @return the number of buttons for the current HID
401   */
402  public int getButtonCount() {
403    return DriverStation.getStickButtonCount(m_port);
404  }
405
406  /**
407   * Get if the HID is connected.
408   *
409   * @return true if the HID is connected
410   */
411  public boolean isConnected() {
412    return DriverStation.isJoystickConnected(m_port);
413  }
414
415  /**
416   * Get the type of the HID.
417   *
418   * @return the type of the HID.
419   */
420  public HIDType getType() {
421    return HIDType.of(DriverStation.getJoystickType(m_port));
422  }
423
424  /**
425   * Get the name of the HID.
426   *
427   * @return the name of the HID.
428   */
429  public String getName() {
430    return DriverStation.getJoystickName(m_port);
431  }
432
433  /**
434   * Get the axis type of the provided joystick axis.
435   *
436   * @param axis The axis to read, starting at 0.
437   * @return the axis type of the given joystick axis
438   */
439  public int getAxisType(int axis) {
440    return DriverStation.getJoystickAxisType(m_port, axis);
441  }
442
443  /**
444   * Get the port number of the HID.
445   *
446   * @return The port number of the HID.
447   */
448  public int getPort() {
449    return m_port;
450  }
451
452  /**
453   * Set a single HID output value for the HID.
454   *
455   * @param outputNumber The index of the output to set (1-32)
456   * @param value The value to set the output to
457   */
458  public void setOutput(int outputNumber, boolean value) {
459    m_outputs = (m_outputs & ~(1 << (outputNumber - 1))) | ((value ? 1 : 0) << (outputNumber - 1));
460    DriverStationJNI.setJoystickOutputs((byte) m_port, m_outputs, m_leftRumble, m_rightRumble);
461  }
462
463  /**
464   * Set all HID output values for the HID.
465   *
466   * @param value The 32 bit output value (1 bit for each output)
467   */
468  public void setOutputs(int value) {
469    m_outputs = value;
470    DriverStationJNI.setJoystickOutputs((byte) m_port, m_outputs, m_leftRumble, m_rightRumble);
471  }
472
473  /**
474   * Set the rumble output for the HID. The DS currently supports 2 rumble values, left rumble and
475   * right rumble.
476   *
477   * @param type Which rumble value to set
478   * @param value The normalized value (0 to 1) to set the rumble to
479   */
480  public void setRumble(RumbleType type, double value) {
481    value = MathUtil.clamp(value, 0, 1);
482    int rumbleValue = (int) (value * 65535);
483    switch (type) {
484      case kLeftRumble -> this.m_leftRumble = rumbleValue;
485      case kRightRumble -> this.m_rightRumble = rumbleValue;
486      default -> {
487        this.m_leftRumble = rumbleValue;
488        this.m_rightRumble = rumbleValue;
489      }
490    }
491
492    DriverStationJNI.setJoystickOutputs(
493        (byte) this.m_port, this.m_outputs, this.m_leftRumble, this.m_rightRumble);
494  }
495}