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.wpilibj2.command.button;
006
007import edu.wpi.first.math.Pair;
008import edu.wpi.first.wpilibj.DriverStation.POVDirection;
009import edu.wpi.first.wpilibj.GenericHID;
010import edu.wpi.first.wpilibj.event.EventLoop;
011import edu.wpi.first.wpilibj2.command.CommandScheduler;
012import java.util.HashMap;
013import java.util.Map;
014
015/**
016 * A version of {@link GenericHID} with {@link Trigger} factories for command-based.
017 *
018 * @see GenericHID
019 */
020public class CommandGenericHID {
021  private final GenericHID m_hid;
022  private final Map<EventLoop, Map<Integer, Trigger>> m_buttonCache = new HashMap<>();
023  private final Map<EventLoop, Map<Pair<Integer, Double>, Trigger>> m_axisLessThanCache =
024      new HashMap<>();
025  private final Map<EventLoop, Map<Pair<Integer, Double>, Trigger>> m_axisGreaterThanCache =
026      new HashMap<>();
027  private final Map<EventLoop, Map<Pair<Integer, Double>, Trigger>>
028      m_axisMagnitudeGreaterThanCache = new HashMap<>();
029  private final Map<EventLoop, Map<Integer, Trigger>> m_povCache = new HashMap<>();
030
031  /**
032   * Construct an instance of a device.
033   *
034   * @param port The port index on the Driver Station that the device is plugged into.
035   */
036  public CommandGenericHID(int port) {
037    m_hid = new GenericHID(port);
038  }
039
040  /**
041   * Get the underlying GenericHID object.
042   *
043   * @return the wrapped GenericHID object
044   */
045  public GenericHID getHID() {
046    return m_hid;
047  }
048
049  /**
050   * Constructs an event instance around this button's digital signal.
051   *
052   * @param button the button index
053   * @return an event instance representing the button's digital signal attached to the {@link
054   *     CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
055   * @see #button(int, EventLoop)
056   */
057  public Trigger button(int button) {
058    return button(button, CommandScheduler.getInstance().getDefaultButtonLoop());
059  }
060
061  /**
062   * Constructs an event instance around this button's digital signal.
063   *
064   * @param button the button index
065   * @param loop the event loop instance to attach the event to.
066   * @return an event instance representing the button's digital signal attached to the given loop.
067   */
068  public Trigger button(int button, EventLoop loop) {
069    var cache = m_buttonCache.computeIfAbsent(loop, k -> new HashMap<>());
070    return cache.computeIfAbsent(button, k -> new Trigger(loop, () -> m_hid.getRawButton(k)));
071  }
072
073  /**
074   * Constructs a Trigger instance based around this angle of the default (index 0) POV on the HID,
075   * attached to {@link CommandScheduler#getDefaultButtonLoop() the default command scheduler button
076   * loop}.
077   *
078   * @param angle POV angle.
079   * @return a Trigger instance based around this angle of a POV on the HID.
080   */
081  public Trigger pov(POVDirection angle) {
082    return pov(0, angle, CommandScheduler.getInstance().getDefaultButtonLoop());
083  }
084
085  /**
086   * Constructs a Trigger instance based around this angle of a POV on the HID.
087   *
088   * @param pov index of the POV to read (starting at 0). Defaults to 0.
089   * @param angle POV angle.
090   * @param loop the event loop instance to attach the event to. Defaults to {@link
091   *     CommandScheduler#getDefaultButtonLoop() the default command scheduler button loop}.
092   * @return a Trigger instance based around this angle of a POV on the HID.
093   */
094  public Trigger pov(int pov, POVDirection angle, EventLoop loop) {
095    var cache = m_povCache.computeIfAbsent(loop, k -> new HashMap<>());
096    // angle.value is a 4 bit bitfield
097    return cache.computeIfAbsent(
098        pov * 16 + angle.value, k -> new Trigger(loop, () -> m_hid.getPOV(pov) == angle));
099  }
100
101  /**
102   * Constructs a Trigger instance based around the up direction of the default (index 0) POV on the
103   * HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command scheduler
104   * button loop}.
105   *
106   * @return a Trigger instance based around the up direction of a POV on the HID.
107   */
108  public Trigger povUp() {
109    return pov(POVDirection.Up);
110  }
111
112  /**
113   * Constructs a Trigger instance based around the up right direction of the default (index 0) POV
114   * on the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
115   * scheduler button loop}.
116   *
117   * @return a Trigger instance based around the up right direction of a POV on the HID.
118   */
119  public Trigger povUpRight() {
120    return pov(POVDirection.UpRight);
121  }
122
123  /**
124   * Constructs a Trigger instance based around the right direction of the default (index 0) POV on
125   * the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
126   * scheduler button loop}.
127   *
128   * @return a Trigger instance based around the right direction of a POV on the HID.
129   */
130  public Trigger povRight() {
131    return pov(POVDirection.Right);
132  }
133
134  /**
135   * Constructs a Trigger instance based around the down right direction of the default (index 0)
136   * POV on the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
137   * scheduler button loop}.
138   *
139   * @return a Trigger instance based around the down right direction of a POV on the HID.
140   */
141  public Trigger povDownRight() {
142    return pov(POVDirection.DownRight);
143  }
144
145  /**
146   * Constructs a Trigger instance based around the down direction of the default (index 0) POV on
147   * the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
148   * scheduler button loop}.
149   *
150   * @return a Trigger instance based around the down direction of a POV on the HID.
151   */
152  public Trigger povDown() {
153    return pov(POVDirection.Down);
154  }
155
156  /**
157   * Constructs a Trigger instance based around the down left POV direction of the default (index 0)
158   * POV on the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
159   * scheduler button loop}.
160   *
161   * @return a Trigger instance based around the down left POV direction of a POV on the HID.
162   */
163  public Trigger povDownLeft() {
164    return pov(POVDirection.DownLeft);
165  }
166
167  /**
168   * Constructs a Trigger instance based around the left direction of the default (index 0) POV on
169   * the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
170   * scheduler button loop}.
171   *
172   * @return a Trigger instance based around the left direction of a POV on the HID.
173   */
174  public Trigger povLeft() {
175    return pov(POVDirection.Left);
176  }
177
178  /**
179   * Constructs a Trigger instance based around the up left direction of the default (index 0) POV
180   * on the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
181   * scheduler button loop}.
182   *
183   * @return a Trigger instance based around the up left direction of a POV on the HID.
184   */
185  public Trigger povUpLeft() {
186    return pov(POVDirection.UpLeft);
187  }
188
189  /**
190   * Constructs a Trigger instance based around the center (not pressed) position of the default
191   * (index 0) POV on the HID, attached to {@link CommandScheduler#getDefaultButtonLoop() the
192   * default command scheduler button loop}.
193   *
194   * @return a Trigger instance based around the center position of a POV on the HID.
195   */
196  public Trigger povCenter() {
197    return pov(POVDirection.Center);
198  }
199
200  /**
201   * Constructs a Trigger instance that is true when the axis value is less than {@code threshold},
202   * attached to {@link CommandScheduler#getDefaultButtonLoop() the default command scheduler button
203   * loop}.
204   *
205   * @param axis The axis to read, starting at 0
206   * @param threshold The value below which this trigger should return true.
207   * @return a Trigger instance that is true when the axis value is less than the provided
208   *     threshold.
209   */
210  public Trigger axisLessThan(int axis, double threshold) {
211    return axisLessThan(axis, threshold, CommandScheduler.getInstance().getDefaultButtonLoop());
212  }
213
214  /**
215   * Constructs a Trigger instance that is true when the axis value is less than {@code threshold},
216   * attached to the given loop.
217   *
218   * @param axis The axis to read, starting at 0
219   * @param threshold The value below which this trigger should return true.
220   * @param loop the event loop instance to attach the trigger to
221   * @return a Trigger instance that is true when the axis value is less than the provided
222   *     threshold.
223   */
224  public Trigger axisLessThan(int axis, double threshold, EventLoop loop) {
225    var cache = m_axisLessThanCache.computeIfAbsent(loop, k -> new HashMap<>());
226    return cache.computeIfAbsent(
227        Pair.of(axis, threshold), k -> new Trigger(loop, () -> getRawAxis(axis) < threshold));
228  }
229
230  /**
231   * Constructs a Trigger instance that is true when the axis value is less than {@code threshold},
232   * attached to {@link CommandScheduler#getDefaultButtonLoop() the default command scheduler button
233   * loop}.
234   *
235   * @param axis The axis to read, starting at 0
236   * @param threshold The value above which this trigger should return true.
237   * @return a Trigger instance that is true when the axis value is greater than the provided
238   *     threshold.
239   */
240  public Trigger axisGreaterThan(int axis, double threshold) {
241    return axisGreaterThan(axis, threshold, CommandScheduler.getInstance().getDefaultButtonLoop());
242  }
243
244  /**
245   * Constructs a Trigger instance that is true when the axis value is greater than {@code
246   * threshold}, attached to the given loop.
247   *
248   * @param axis The axis to read, starting at 0
249   * @param threshold The value above which this trigger should return true.
250   * @param loop the event loop instance to attach the trigger to.
251   * @return a Trigger instance that is true when the axis value is greater than the provided
252   *     threshold.
253   */
254  public Trigger axisGreaterThan(int axis, double threshold, EventLoop loop) {
255    var cache = m_axisGreaterThanCache.computeIfAbsent(loop, k -> new HashMap<>());
256    return cache.computeIfAbsent(
257        Pair.of(axis, threshold), k -> new Trigger(loop, () -> getRawAxis(axis) > threshold));
258  }
259
260  /**
261   * Constructs a Trigger instance that is true when the axis magnitude value is greater than {@code
262   * threshold}, attached to the given loop.
263   *
264   * @param axis The axis to read, starting at 0
265   * @param threshold The value above which this trigger should return true.
266   * @param loop the event loop instance to attach the trigger to.
267   * @return a Trigger instance that is true when the axis magnitude value is greater than the
268   *     provided threshold.
269   */
270  public Trigger axisMagnitudeGreaterThan(int axis, double threshold, EventLoop loop) {
271    var cache = m_axisMagnitudeGreaterThanCache.computeIfAbsent(loop, k -> new HashMap<>());
272    return cache.computeIfAbsent(
273        Pair.of(axis, threshold),
274        k -> new Trigger(loop, () -> Math.abs(getRawAxis(axis)) > threshold));
275  }
276
277  /**
278   * Constructs a Trigger instance that is true when the axis magnitude value is greater than {@code
279   * threshold}, attached to {@link CommandScheduler#getDefaultButtonLoop() the default command
280   * scheduler button loop}.
281   *
282   * @param axis The axis to read, starting at 0
283   * @param threshold The value above which this trigger should return true.
284   * @return a Trigger instance that is true when the deadbanded axis value is active (non-zero).
285   */
286  public Trigger axisMagnitudeGreaterThan(int axis, double threshold) {
287    return axisMagnitudeGreaterThan(
288        axis, threshold, CommandScheduler.getInstance().getDefaultButtonLoop());
289  }
290
291  /**
292   * Get the value of the axis.
293   *
294   * @param axis The axis to read, starting at 0.
295   * @return The value of the axis.
296   */
297  public double getRawAxis(int axis) {
298    return m_hid.getRawAxis(axis);
299  }
300
301  /**
302   * Set the rumble output for the HID. The DS currently supports 2 rumble values, left rumble and
303   * right rumble.
304   *
305   * @param type Which rumble value to set
306   * @param value The normalized value (0 to 1) to set the rumble to
307   */
308  public void setRumble(GenericHID.RumbleType type, double value) {
309    m_hid.setRumble(type, value);
310  }
311
312  /**
313   * Get if the HID is connected.
314   *
315   * @return true if the HID is connected
316   */
317  public boolean isConnected() {
318    return m_hid.isConnected();
319  }
320}