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}