Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] XboxController joystick reporting nonzero values after release #1473

Closed
gyenesvi opened this issue Feb 23, 2024 · 21 comments · Fixed by pybricks/pybricks-micropython#238
Labels
3rd party devices Issues about 3rd-party sensor, motors and hubs topic: remote control Issues related to remotly controlling hubs

Comments

@gyenesvi
Copy link

When I release the joystick on an Xbox controller, it still seems to be reporting nonzero values, even though it snaps back to the center.

Because the Technic Hub needs to disconnect to allow the Xbox controller to connect, it is a bit tricky to actually show this, because this way the joystick values cannot be printed. The way I realized this is that I was doing speed control (motor.run(speed)) on a PU XL motor, and even when the joystick was released, the motor was moving very slowly.
To do this, first I measured the max speed (around 270 deg/s), and then set the speed proportional to the joystick position, like motor.run(270 * (joystick / 100)). Note, that motor.dc(joystick) would not show this behavior, because of the dead zone of the motor itself, it does not start moving for small power values, but with speed control, it can move very slowly as well.

@gyenesvi gyenesvi added the triage Issues that have not been triaged yet label Feb 23, 2024
@laurensvalk
Copy link
Member

This is known as controller drift in the gaming community.

The joysticks don't go all the way to the perfect zero due to mechanical friction.

In some upcoming examples, I've added an if/else check to ignore very small inputs. Since the motors don't run great at very low speeds, this would probably have been needed anyway, depending on the application.

As I write this, I suppose we could add some code to make small inputs always zero for simplicity. Would this be desirable?

What should be the threshold? Maybe it could be a keyword argument defaulting to 10%? Or 5%?

@laurensvalk laurensvalk added topic: remote control Issues related to remotly controlling hubs 3rd party devices Issues about 3rd-party sensor, motors and hubs and removed triage Issues that have not been triaged yet labels Feb 24, 2024
@BertLindeman
Copy link

On a windows pc you can install "Xbox accessories".
Connected to the xbox controller you can do a recallibrate to have the joysticks return better to zero.
I must add that the program is poorly written. I need to use it full screen or it's useless and even then parts fall of the screen.

In my case a default of 5 would be enough most of the time. Sometimes they stick at 6 or seven, but a nudge keeps them within -5 and 5.

I would like that threshold very much.

Thanks you.

Bert

@gyenesvi
Copy link
Author

This is known as controller drift in the gaming community.

Oh, I see

As I write this, I suppose we could add some code to make small inputs always zero for simplicity. Would this be desirable?

Yes, that's what I did in my code as well, but it would be nice if I didn't have to do that in every program for every joystick

What should be the threshold? Maybe it could be a keyword argument defaulting to 10%? Or 5%?

5% worked also for me, I think that's a good value for a default. It could be a keyword argument like joystick_deadzone in the XboxController class constructor?

@laurensvalk
Copy link
Member

Connected to the xbox controller you can do a recallibrate to have the joysticks return better to zero.

Does this affect the numbers in Pybricks too? I'm asking because it's possible that this sets other mapping values on the controller that we might not currently use.

With a Spike Hub or Robot Inventor hub you will stay connected, so you could print values to find out.

@BertLindeman
Copy link

Connected to the xbox controller you can do a recallibrate to have the joysticks return better to zero.

Does this affect the numbers in Pybricks too? I'm asking because it's possible that this sets other mapping values on the controller that we might not currently use.

With a Spike Hub or Robot Inventor hub you will stay connected, so you could print values to find out.

I already did that.
There is no difference in the values seen on the RobotInventor.
All joystick values -100 through +100.

For fun I had the xbox controller connected to a Technichub that transmits buttons and such to the robot inventor that printed to the beta.pybricks.com

@laurensvalk
Copy link
Member

I already did that.

I meant that I was wondering if the zero return was better in Pybricks if you changed the controller configuration like you did.

@laurensvalk
Copy link
Member

laurensvalk commented Feb 26, 2024

I've done some more experimenting, and I suppose that the deadzone rounding to 0 should be a fairly good solution.

You'd lose a few degrees of precision at the very center, but this is hardly noticeable even in steering applications.

I'd like the default threshold to be "good enough for most things", so the block language wouldn't need an option for it.

To make things better, we can ensure to round only if both coordinates are near 0 at the same time. This way we don't have to round Y in the case of (X=100, Y=3), for example.

The highest value I could get my controller to stick at was 6% degrees.

5% worked also for me, I think that's a good value for a default. It could be a keyword argument like joystick_deadzone in the XboxController class constructor?

I was originally thinking to add it to the joystick methods, but maybe the controller setup is a good place for it indeed. Thanks!

@laurensvalk
Copy link
Member

While we are at it, any opinions on the resolution? We could theoretically get about 10x more steps, e.g 12.3%. Would this be useful at all?

@gyenesvi
Copy link
Author

To make things better, we can ensure to round only if both coordinates are near 0 at the same time.

I think that makes sense.

I'd like the default threshold to be "good enough for most things", so the block language wouldn't need an option for it. (...) I was originally thinking to add it to the joystick methods, but maybe the controller setup is a good place for it indeed.

I agree this could be left out of the block coding language to keep it simple. Though, maybe, if it's a constructor parameter, then it can even be included, does not clutter too much there. It's up to you.

While we are at it, any opinions on the resolution? We could theoretically get about 10x more steps, e.g 12.3%. Would this be useful at all?

What's the current resolution? 1%? And how is it represented, as ints or as floats? Do you mean going down to 0.1%? Wouldn't that complicate things somewhat? I mean you'd either have to represent them as floats, or in units that would be less clear to understand, i.e. would not mean percentages anymore.

I am actually okay with the current resolution in terms of steering/throttle performance, and I don't think anyone could feel the difference less than 1% in the joystick position.

@laurensvalk
Copy link
Member

The joystick values are 16 bit, so technically up to 32768 values. The triggers are 10 bit, so 0--1024.

We've scaled everything to percentages for simplicity. We'll keep it that way, but we could choose to return a floating point value to keep the resolution.

Indeed, so far I've found that 100 steps is enough, but I was curious about other opinions since we're going to slightly change these methods now anyway.

@BertLindeman
Copy link

I already did that.

I meant that I was wondering if the zero return was better in Pybricks if you changed the controller configuration like you did.

Sorry I was not clear in this Laurens. I meant to say I did the recalibrate and then checked with pybricks.
There was no noticable difference. Could be because the controller is new and not so exact on return to zero.

@laurensvalk
Copy link
Member

Thanks for confirming!

@BertLindeman
Copy link

I agree with Victor about the resolution. Percentage as integer is good enough, I think.

@BertLindeman
Copy link

Maybe helpfull: I asked Copilot (AI assistent)
Got (more or less) this response:

The size of the deadzone can vary depending on the specific controller and the game or application it’s being used with.

What do we suggest?
We think you should work with 0.15 (15 percent).
Why? Because it is far easier to control your car this way.

So I might ajust my default drift dead zone to 10%

@gyenesvi
Copy link
Author

Interestingly, most players on the link use cross shaped deadzone. Is that the opposite of what we thought might be good above (clip one small value even if the other value is large)? I could also imagine that one making sense, although only makes a difference when one uses a joystick in both dimensions, which I rarely do with my lego builds, so cannot judge.

@laurensvalk
Copy link
Member

My suggestion was for two dimensions. Thanks for the links! This looks like quite another rabbit hole 😄

I think a circle or square makes a bit more sense (compared to a cross shaped zone).

Our main purpose is only to eliminate the effects of drift, which is what these will do. If you also want to zero out X when Y is small as in a cross, it should be easy enough to do in user code.

@laurensvalk
Copy link
Member

laurensvalk commented Feb 27, 2024

The pull request here introduces a 10% deadzone to suppress drift.

It is configurable, i.e. XboxController(joystick_deadzone=10)

You can download the read-made firmware from the post above in a few minutes.

@BertLindeman
Copy link

Tested with spike hub and this program:

from pybricks.iodevices import XboxController
from pybricks.tools import wait

print("\x1b[H\x1b[2J", end="")  # clear the log

# Set up all devices.
controller = XboxController()


# The main program starts here.
while True:    
    print("\x1b[H", end="")  # Go to top of the screen
    print(f'left  trigger           {controller.triggers()[0]:>20}',
          f'right trigger           {controller.triggers()[1]:>20}')
    print(f'left  joystick hor      {controller.joystick_left()[0]:>20}',
          f'right joystick hor      {controller.joystick_right()[0]:>20}')
    print(f'left  joystick vertical {controller.joystick_left()[1]:>20}',
          f'right joystick vertical {controller.joystick_right()[1]:>20}')
    print(f'D-pad                   {controller.dpad():>20}')
    print(f'profile                 {controller.profile():>20}')
    if controller.buttons.pressed():
        print(f'Buttons pressed         {str(controller.buttons.pressed()):<50}')
    else:
        print(f'{"Buttons pressed":<70}')
    wait(250)

Noticed that a joystick reported horizontal 10 if released but the vertical was less than 10.
Assume that is outside the dead zone circle.

Tested with drift=10 with the default and drift=1000
Silently cut off at or near 100 I think.
Look good to me.

@gyenesvi
Copy link
Author

Could not test yet, but the name drift is not very informative for me; it does not tell that it's a deadzone/threshold parameter, neither that it only applies for joysticks, and not for the triggers for example.

@laurensvalk
Copy link
Member

Updated PR based on feedback.

@gyenesvi
Copy link
Author

Great, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3rd party devices Issues about 3rd-party sensor, motors and hubs topic: remote control Issues related to remotly controlling hubs
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants