In this session, we accomplished:
- Reading the value of a button on the joystick in different ways
- Reading the value of an axis on the joystick
- Reading the value of the POV on the joystick
Last time, we saw that you can report if a button is currently being pressed using the getRawButton
method of a Joystick
:
@Override
public void robotPeriodic() {
boolean button = testJoystick.getRawButton(1);
SmartDashboard.putBoolean("Button", button);
}
The key thing to know about getRawButton
is that it will only tell you if the button is currently pressed or not. Unfortunately, a robot does not process time exactly the same way that a person does. For example, when I press a button, I can know if it is pressed or not the entire time that I am pressing it. Robot code processes time a little differently.
Remember that the periodic
functions only run one time every (approximately) 20 ms. This means that the robot can only check if the button has been pressed every 20 ms. It's possible to press and release the button so quickly that the robot misses the press entirely - meaning you could try to tell the robot to do something (by pressing a button), but it would never see the button press, so it wouldn't do what you expected.
Fortunately, WPILib has a couple of tools to help with this problem. One is the getRawButtonPressed
method, which you can use almost exactly like the getRawButton
method:
@Override
public void robotPeriodic() {
// Get Raw Button
boolean button = testJoystick.getRawButton(1);
SmartDashboard.putBoolean("Button", button);
// Get Raw Button Pressed
boolean pressed = testJoystick.getRawButtonPressed(1);
SmartDashboard.putBoolean("Pressed", pressed);
}
As we saw before, the method will give us a boolean
value (meaning a true
or false
) value, so we can use SmartDashboard
's putBoolean
method to show it. We do have to use a different key ("Pressed"
vs. "Button"
) so that we get two different tiles on Shuffleboard instead of one. Up to this point, though, it should all look almost the same when you simulate the code - but there is a difference in behavior.
While getRawButton
will tell you, "Is the button currently pressed," the new method getRawButtonPressed
will actually tell you, "Has the button changed from not pressed
to pressed
since the last time we checked." This will allow the button presses to register with the robot code even if they happen between those 20 ms periodic
checks.
For that reason, you will usually prefer getRawButtonPressed
to getRawButton
, but there's another problem to consider - this time, a human one.
Often times, a person can press a button just a little too early, causing an action to occur before they really intended. The getRawButtonPressed
method does not provide much safety against this scenario - but there is a method that does. The getRawButtonReleased
method works by reporting when a button has gone from being pressed to not being pressed, and you use it almost exactly how you use the other two methods.
@Override
public void robotPeriodic() {
// Get Raw Button
boolean button = testJoystick.getRawButton(1);
SmartDashboard.putBoolean("Button", button);
// Get Raw Button Pressed
boolean pressed = testJoystick.getRawButtonPressed(1);
SmartDashboard.putBoolean("Pressed", pressed);
// Get Raw Button Released
boolean released = testJoystick.getRawButtonReleased(1);
SmartDashboard.putBoolean("Released", released);
}
Using this method, if an operator presses the button too early, they are able to hold the button down and wait to let go when the robot is in the actual position they want. This is works because getRawButtonReleased
does not register as true
until the button gets let go, while the other methods register as true
when the button gets pressed.
Using these three methods works with any buttons on the joystick - just remember that buttons do not include anything that "wiggles" (like a thumbstick or a trigger), nor does it include anything resembling a D-Pad. The joysticks we typically use off-the-shelf have about 10 buttons total, and the first button is button 1
. As a reminder, you can figure out which button has which number by plugging in the joystick and looking in Driver Station.
Again, in all three cases, the button is either in that state (currently pressed, changed from not-pressed to pressed, or changed from pressed to not-pressed), which is true
, or the button is not in that state, which registers false
. For this reason, we capture the value of the method as a boolean
in all three cases.
When working with buttons, it made sense to use boolean
values because there are only two possible states for the button. There are many times where we have a limited number of possible outcomes. We say that these scenarios are countable - we can count (easily) how many possible outcomes there are. We also say that the values are discrete - meaning that the states are completely separate and distinct. For example, a button cannot be both pressed and not pressed - it can only be one. This is similar to the concept of something being digital - which we will see again in later concepts.
For other things we want to examine, it may not be possible to list out (or enumerate) all possible states. It could be that our measurement could take on any value within a range of (nearly) infinitely many values. In this case, the values are not quite discrete, they are much closer to the concept of being continuous and are more similar to the concept of analog in electronics.
While a button is like the discrete example, an axis is more like the continuous example. On a gamepad, you can find axes in the triggers and on the thumbsticks. On a flightstick, you can find axes in the joystick itself and in any sliders or paddles. In general, axes can give a value between -1.0
and +1.0
, though some only go between -1.0
and 0.0
or 0.0
and +1.0
. Most axes, by default, will start at 0.0
, but they can take on any values in those range.
It's also important to know that some parts of a joystick may be made of multiple axes. For example, the left thumb stick on a gamepad has one axis for the left-right motion and another for the up-down motion. You can see which axis maps to which component/direction in Driver Station, the same way you did for buttons. Just know that while buttons started at button 1
, axes start with axis 0
.
We will start by reading off a value from axis 0
. While buttons had only two possible states and we were able to use a boolean
to capture that value, axes report their values as decimal numbers - which we will refer to as real numbers. These real numbers can be positive, negative, or zero, and may include a decimal point. In Java, we use the double
data type to hold real numbers. (In earlier programming days, we used a type called a float
- which can still be used today - but it was later replaced by a data type that could store twice the information, hence double
.) Because double
is just another type in Java, we can store values in a similar way.
@Override
public void robotPeriodic() {
// Code for buttons is above somewhere
...
double axis0 =
}
To read an axis from a Joystick
, we use the getRawAxis
method for axis 0
.
@Override
public void robotPeriodic() {
// Code for buttons is above somewhere
...
double axis0 = testJoystick.getRawAxis(0);
}
We can also use the SmartDashboard
reference to show the value to the user, but we can't use putBoolean
, because the value we have is not a boolean
. Instead, when putting numbers on display in Shuffleboard, we want to use the putNumber
method, which takes a key and value just like putBoolean
did. The only difference is that the value we give it must be a number of some type (like a double
), not a boolean
.
@Override
public void robotPeriodic() {
// Code for buttons is above somewhere
...
double axis0 = testJoystick.getRawAxis(0);
SmartDashboard.putNumber("Axis 0", axis0);
}
When you simulate this code in Driver Station, Shuffleboard will add a tile. Moving the axis to and fro will change the value displayed, similar to how the buttons worked - just with a different format of display.
In general, left-right axes will use -1.0
for full left and +1.0
for full right. Up-down axes will use -1.0
for full up and +1.0
for full down. The reason for this convention goes back to how pilots control aircraft, which is why the value might seem inverted.
Axes usually report 0
in the center of their travel (e.g., the exact middle of left and right), but axes may report 0
for a range of values. This is what we call a deadband. It basically means that if the axis is close enough to the 0
position, then it will report 0
as its value. This could be the first 10% of motion on either side or some other custom value. This is something that can be configured in many cases, and we'll look at it more in depth at some point.
The last part of the joystick to consider is the directional-pad (sometimes called the D-Pad). In WPILib, this part of the controller is referred to as a POV
(point-of-view). This component is not treated like a button, nor is it treated like an axis - it behaves differently from either.
Like an axis, the POV can take on several different values - but like a button there is a limited number of them (they are discrete values). Its value is always reported as a number, so a boolean
is not the right data type to use. You might think to use a double
, but these values never have a decimal value - so a double
would be overkill. Instead, the POV always reports an integer value, that is, a positive number, a negative number, or zero with no decimal component. This means the POV could take on a value like 5
but not 5.4
. When working with integer values, we will most often use the int
data type.
@Override
public void robotPeriodic() {
// Code for buttons is above somewhere
// Code for axes is above somewhere
...
int pov =
The value reported by the POV will be an integer that represents which direction the POV is pressed in as an angle between 0
and 360
degrees (not including 360
), using the value -1
for a POV that is not pressed at all. The up direction is 0
degrees and the angle increases clockwise. This is the same angle reference used by pilots, similar to how we saw up-down axes behave.
So here are some common values the POV will report and what they mean:
-1
: the POV is not pressed0
: the POV is pressed up45
: the POV is pressed up and right90
: the POV is pressed right135
: the POV is pressed right and down180
: the POV is pressed down225
: the POV is pressed down and left270
: the POV is pressed left315
: the POV is pressed left and up
Most POVs you encounter will only report one of these nine states, but if the POV has more precision, it could report other values between 0
and 360
degrees.
You can retrieve the value of the POV using the getPOV
method, which will return the value of the controller's default POV component (note that some controller may have more than one POV).
@Override
public void robotPeriodic() {
// Code for buttons is above somewhere
// Code for axes is above somewhere
...
int pov = testJoystick.getPOV();
What's nice here is that SmartDashboard
chose not to distinguish between int
and double
values, since both are numerical, so you can still use the putNumber
method to report the value in Shuffleboard.
@Override
public void robotPeriodic() {
// Code for buttons is above somewhere
// Code for axes is above somewhere
...
int pov = testJoystick.getPOV();
SmartDashboard.putNumber("POV", pov);
Doing so will report a tile similar to the one used for double
s, but you won't see a decimal reported for these int
values.
This wraps up most of the basic functionality you get from a USB joystick. There are more advanced features to general joysticks and more specific ones (like the XboxController
). To learn more about joysticks, you can refer to WPILib's joystick documentation.