darthcloud / BlueRetro

Multiplayer Bluetooth controllers adapter for retro video game consoles
https://blueretro.io
Apache License 2.0
1.23k stars 104 forks source link

Added Rumble Support for generic devices #620

Closed JPZV closed 1 year ago

JPZV commented 1 year ago

This adds support for generic HID controllers to have rumble Support (#611)

Every device which reports usages from 0F 95 to 0F 9C will be marked as a rumble-capable device (hid_parser.c#L196) along with 0F 50, 0F 70, 0F 7C and 0F A7 (hid_parser.c#L197).

Please note that there is no effect support, so usages from 0F 99 to 0F 9C have no use.

The way it works is by getting the Maximum and the Minimum value for the report. In case the Rumble State is off, then the BlueRetro will send the minimum value to every usage. Otherwise, it'll send the maximum value multiplied by a multiplier constant (its value is from 0.0 to 1.0. I leave it there just in case) or the value from fb_data.

This should work with generic controllers like the Stadia one, Chinese clones/alternatives, and projects like my BluControl (TBA) an BluN64, without needing to integrate them separately.

Main Changes:

Tested with:

There shouldn't be any breaking change as I didn't touch anything related to the current functionalities. Also, I didn't note any type of input lag, and I tried to simplify as much as I could.

If you have any question or feedback, please let me know

JPZV commented 1 year ago

Note: The reason about pytest failling is because (I think) it originally didn't count about BlueRetro printing an Output usage.

Below is the test Descriptor parsed by USB Descriptor and Request Parser, and I marked both Output which were ignored on previous version of BlueRetro:

Test Descriptor ``` 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) 0x85, 0x01, // Report ID (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x19, 0xE0, // Usage Minimum (0xE0) 0x29, 0xE7, // Usage Maximum (0xE7) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x95, 0x01, // Report Count (1) 0x75, 0x08, // Report Size (8) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x95, 0x05, // Report Count (5) 0x75, 0x01, // Report Size (1) 0x05, 0x08, // Usage Page (LEDs) 0x19, 0x01, // Usage Minimum (Num Lock) 0x29, 0x05, // Usage Maximum (Kana) 0x91, 0x02, ///////// Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 0x95, 0x01, // Report Count (1) 0x75, 0x03, // Report Size (3) 0x91, 0x03, ///////// Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 0x95, 0x06, // Report Count (6) 0x75, 0x08, // Report Size (8) 0x15, 0x00, // Logical Minimum (0) 0x26, 0xFF, 0x00, // Logical Maximum (255) 0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x19, 0x00, // Usage Minimum (0x00) 0x29, 0xFF, // Usage Maximum (0xFF) 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection 0x05, 0x0C, // Usage Page (Consumer) 0x09, 0x01, // Usage (Consumer Control) 0xA1, 0x01, // Collection (Application) 0x85, 0x02, // Report ID (2) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x16, // Report Count (22) 0x0A, 0xB1, 0x01, // Usage (AL Screen Saver) 0x0A, 0x23, 0x02, // Usage (AC Home) 0x0A, 0xAE, 0x01, // Usage (AL Keyboard Layout) 0x0A, 0x8A, 0x01, // Usage (AL Email Reader) 0x09, 0x40, // Usage (Menu) 0x09, 0x6F, // Usage (0x6F) 0x0A, 0x21, 0x02, // Usage (AC Search) 0x09, 0xB6, // Usage (Scan Previous Track) 0x09, 0xCD, // Usage (Play/Pause) 0x09, 0xB5, // Usage (Scan Next Track) 0x09, 0xE2, // Usage (Mute) 0x09, 0xEA, // Usage (Volume Decrement) 0x09, 0xE9, // Usage (Volume Increment) 0x09, 0x30, // Usage (Power) 0x0A, 0x83, 0x01, // Usage (AL Consumer Control Configuration) 0x0A, 0x24, 0x02, // Usage (AC Back) 0x0A, 0x06, 0x03, // Usage (0x0306) 0x0A, 0x08, 0x03, // Usage (0x0308) 0x0A, 0x01, 0x03, // Usage (0x0301) 0x0A, 0x83, 0x01, // Usage (AL Consumer Control Configuration) 0x0A, 0x0A, 0x03, // Usage (0x030A) 0x09, 0x70, // Usage (0x70) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x95, 0x01, // Report Count (1) 0x75, 0x02, // Report Size (2) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection // 147 bytes ```

I didn't change the pytest files because I think that should be done by the maintainers after they accept the Pull, but it's just about updating the expecting string to 1 07E0 0 8 0801 16 5 0700 24 8, 0700 32 8, 0700 40 8, 0700 48 8, 0700 56 8, 0700 64 8, rtype: 0 dtype: 0 sub: 0 (the only thing that changes is the addition of 0801 16 5 and the consecutive offsets)

darthcloud commented 1 year ago

It's minor but in my view a PR should make sure if they affect the test result expectation to modify them to the new expectation.

This look really good. Does that make both left & right motor work in a sensible way?

I wonder if that would work better than whatever I make for xbox?

With an extra commit to fix the test, I would merge this.

JPZV commented 1 year ago

It's minor but in my view a PR should make sure if they affect the test result expectation to modify them to the new expectation.

Yeah, I think the same. My guess is that the test didn't expect to read an output usage so that's why it's failing now, as BlueRetro printed an output that were ignored before. (As you can see, before every output usage would be completely ignored, while now it'll be threated as the same way as an input)

This look really good. Does that make both left & right motor work in a sensible way?

It should, but I need more devices to test it. My code assumes that every report field is a motor, so if there are five rumble motors, they all will be turned on/off in the same way.

Right now BlueRetro doesn't pass much information to handle them separately. But I tried to use them all, like the delay, the cycle count, and the duration. Some of them I get from BlueRetro itself while the others are from Max/Min values.

I wonder if that would work better than whatever I make for xbox?

I actually based my code from the Xbox one, and I thought the exactly the same, but, again, I don't have any Xbox One controller so I cannot test it neither.

With an extra commit to fix the test, I would merge this.

As I said before, the only thing to "fix" the test should be to update the expected output adding the new Output usage

darthcloud commented 1 year ago

Yes I know, I would expect you to be the one to modify that. You are the owner of the PR and it's failing the test ;)

JPZV commented 1 year ago

Ok, I modified the expected output according to what I get with a BlueRetro running on a ESP32 and with a BT Device with the exactly the same descriptor as the test. Right now it should be doing the test and I hope it should end successfully

darthcloud commented 1 year ago

Ok I will look at more closely later, It doesn't make sense for the report # 1 usage offset to change IMHO.

The rumble stuff should be like in another report.

JPZV commented 1 year ago

I think the output is on report # 1 because they're declared before changing the ID.

image

As you can see, all of the inputs (Red) and both Outputs (Blue) are on the same Report (Yellow/1) as the Report 2 was declared after (Orange).

About getting rumble from another report, that's completely from the device's side, as the manufacture defines the structure. For example, in Stadia, Google didn't use Collections while Microsoft did. The same thing for Output reports. In a perfect world, yes indeed, those reports should be in a different report, but you know where we live right now ¯_(ツ)_/¯

darthcloud commented 1 year ago

Sorry for my confusion, in my mind the LEDs output case was handled properly all along. So I was surprised when I saw it popping in and shifting all other key usage bit range by one bytes.

That means keyboard where kind of broken since day one but it was kind of ok since having the Leds byte as the first key didn't make anything, and missing the last key just reduced the amount of simultaneous key press by one.

Anyway your PR fix this!

darthcloud commented 1 year ago

Thanks again for the PR!

JPZV commented 1 year ago

No problem! I'm very happy to support this amazing project!!!

DJm00n commented 1 year ago

Wow. Nice! Thank you @JPZV! I'll give it a try with my Stadia Controller.

DJm00n commented 1 year ago

@JPZV Seems vibration still not working with Stadia Controller. See #578.