zeth / inputs

Cross-platform Python support for keyboards, mice and gamepads
BSD 3-Clause "New" or "Revised" License
273 stars 88 forks source link

Rumble support? #43

Closed toonn closed 6 years ago

toonn commented 6 years ago

Does inputs support the rumble functionality in most gamepads? I realize that's output rather than input but it still seems appropriate.

Thank you for the library! I'm using it to write a scratch 2.0 extension so our CoderDojo can have kids use the full functionality of the gamepads; currently we're limited to mapping buttons to keyboard keys and one of the sticks to the mouse with third party software. All I'm missing is rumble functionality.

One question though, is there a list of what all the ev_types and codes mean? For example, I currently completely ignore SYN_DROPPED and others, only use events with types Key and Absolute actually. I'm not sure how robust this 🙈 approach is.

toonn commented 6 years ago

Oh and how about sixaxis support? This'd just be a bonus though.

zeth commented 6 years ago

Your extension sounds great.

Rumble support on the Xbox controller should be possible since I have used that before.

Sixaxis should work over USB, Bluetooth support is on my TODO list.

Your approach is probably fine. Ignore events that you do not care about. I throw everything that evdev gives into InputEvent objects (and emulate that behaviour on Windows+Mac), sync and dropped etc are a bit over-specific for users to care about.

zeth commented 6 years ago

So Rumble already works on Windows. import inputs gamepad = inputs.devices.gamepads[0] gamepad.set_vibration(1,1)

to stop it gamepad.set_vibration(0,0)

toonn commented 6 years ago

Oh, cool. Most kids'll be running windows anyway. Just makes it harder for me to test 🤷‍♂️. Is the speed variable between 0 and 1, or multiples of 1?

toonn commented 6 years ago

Seems like it's between 0 and 1. Could you throw a NotImplementedException instead of/in addition to printing that the feature is not supported? Rn it's hard to programmatically verify whether the function works or not so I have to duplicate the platform check.

zeth commented 6 years ago

Ok will do. I just managed to make rumble work on Linux so will tidy up that and add it to inputs soon.

zeth commented 6 years ago

Hi @toonn

So on Linux, it seems a duration must be provided, so the method is now:

gamepad.set_vibration(left_motor, right_motor, duration)

That method on Windows sleeps for duration between start and stop.

If you want the old behaviour on Windows use:

gamepad._start_vibration_win(left_motor, right_motor)

And it will run forever, or until you call:

gamepad._stop_vibration_win()

zeth commented 6 years ago

So the code is now in github version, it will be in a new release on pypi in a week or two.

toonn commented 6 years ago

Oh, well that could've saved me some work. I just implemented all the timing necessary around the set(x,y)/set(0,0) calls : )

Is the method synchronous? Can it be cancelled? What happens if I call it before the last call has finished? Can I read data while rumbling?

Btw, by sixaxis I didn't mean the controller but the rotation/acceleration sensing, is that supported? I couldn't find it in the data but maybe I didn't look hard enough.

toonn commented 6 years ago

Thank you for the work btw, it's very much appreciated.

zeth commented 6 years ago

Ok the whole topic of asyncio and inputs is something I am thinking about for a future task. For now I have made the delay and stop be in a subprocess.

So now using gamepad.set_vibration(left_motor, right_motor, duration) should not block you reading data when rumbling.

If you call gamepad.set_vibration and want to stop it earlier than duration, then you can call gamepad._stop_vibration_win() on Windows.

Actually for cross-platform, you can call gamepad.set_vibration(0, 0, 0) which should reset everything. Might be better I don't know.

toonn commented 6 years ago

Hmm, so if (0, 0, 0) works, would (x, y, z) also work, where those are different from the previous call?

Did you miss the sixaxis part of my question?

zeth commented 6 years ago

Well the Xbox 360 controller has no motion detection. The playstation ones do, they probably report those events on Linux. I have it in my todo list to study those controllers (also see #29 ).

zeth commented 6 years ago

If you use gamepad.set_vibration(left_motor, right_motor, duration), then a new call will override the previous call, however, on Windows, it is theoretically possible to get a race condition if you do: start A - duration 3 secs start B - duration 5 secs end A - happens killing the last 2 seconds of B

zeth commented 6 years ago

So you can either manage the delays yourself by using the _start and _stop methods, or let the user figure it out. I think with scratch the user can replay and see (I am a scratch fan too). However, if it proves a problem, I can look into killing A's reset when B happens.

dlernstrom commented 5 years ago

Hey @zeth: So I've been looking at integrating a Logitech Wireless Gamepad F710 for a Raspberry Pi project I'm working on. I didn't want to implement the interface to the gamepad if I could avoid it, and was pleasantly surprised to find this project. I was able to test the gamepad and have very precise data coming in as I test all of the buttons and such. GREAT WORK!

Though I don't need it, I also attempted to test rumble support -- it didn't work. Here is my traceback:

>>> pads[0].set_vibration(1, 1, 1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pi/.virtualenvs/myproj/lib/python3.5/site-packages/inputs.py", line 2983, in set_vibration
    self._set_vibration_nix(left_motor, right_motor, duration)
  File "/home/pi/.virtualenvs/myproj/lib/python3.5/site-packages/inputs.py", line 2969, in _set_vibration_nix
    code = self.__get_vibration_code(left_motor, right_motor, duration)
  File "/home/pi/.virtualenvs/myproj/lib/python3.5/site-packages/inputs.py", line 2963, in __get_vibration_code
    buf_conts = ioctl(self._write_device, 1076905344, inner_event)
OSError: [Errno 14] Bad address

As I have studied the voodoo that is going on there, it appears that you had implemented the ff_rumble_effect, however, when calculating the _IOW Linux macro, you came up with 1076905344. When I did a similar exercise (based upon Linux C code here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input.h), I came up with 1076643200. Simply changing the constant to my calculated value allowed the rumble feature to work for me quite nicely.

HOWEVER, the magic number in the code doesn't really contribute to solid software maintainability. Additionally, as I studied what is occurring in this code, it appears that a new rumble object is defined and sent to the gamepad every time you call set_vibration. On my device, it starts to fail with a memory error after about 4-5 repeated calls.

>>> pads[0].set_vibration(1, 1, 500)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/pi/myproj/inputs.py", line 3223, in set_vibration
    self._set_vibration_nix(left_motor, right_motor, duration)
  File "/home/pi/myproj/inputs.py", line 3191, in _set_vibration_nix
    code = self.__get_vibration_code(left_motor, right_motor, duration)
  File "/home/pi/myproj/inputs.py", line 3185, in __get_vibration_code
    buf_conts = ioctl(self._write_device, 1076643200, inner_event)
OSError: [Errno 28] No space left on device

Now, I definitely understand that voodoo and am willing to help, however, don't want to stick my nose where it doesn't belong. I can certainly cobble together a PR, but from what I can tell (studying this code: https://github.com/flosse/linuxconsole/blob/master/utils/fftest.c), it appears that there are varying rumble mechanisms supported by various gamepads. Coming up with a universal rumble pattern may be tricky and so a more dynamic definition based upon connected devices might make more sense. This is probably why the rumble PR you submitted worked for you but not for me (though from what I can read on the struct definition, I still think you would have calculated the same magic number as I did, so I am not sure why mine is different).

Anyway - how would you like to proceed? How can I help?