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

fix(usb): detect USB power mode to fallback to BLE #2458

Merged

Conversation

angweekiat
Copy link
Contributor

@angweekiat angweekiat commented Sep 4, 2024

Update zmk_usb_is_hid_ready to check that HID configuration from the host has been done, before allowing the keyboard to be used as a HID device.

This is done by adding bool is_configured and toggling it off when the device is disconnected, and enabling it when usb_status == USB_DC_CONFIGURED.

This is a naive solution for #841, where having a USB-power cable without data driving the keyboard, like a power bank, still has the firmware thinking that it's connected to a host and futilely sending data over USB.

When the cable to a power source is plugged in, the USB DC status (from Zephyr) goes through the following states:

USB_DC_CONNECTED -> USB_DC_SUSPEND

While for cables to a host machine:

USB_DC_CONNECTED -> USB_DC_SUSPEND -> USB_DC_RESUME -> USB_DC_RESET -> USB_DC_CONFIGURED

The status code list can be found at https://github.com/zephyrproject-rtos/zephyr/blob/main/include/zephyr/drivers/usb/usb_dc.h#L33-L58

In the case of a power bank being plugged in, the code changes above allow zmk_usb_is_hid_ready to declare that HID isn't ready yet, and thus allows endpoints.c to fallback to BLE connection.

Steps to reproduce the original issue

  1. (Setup) Have ZMK keyboard with Bluetooth profile enabled
  2. Set the keyboard to prefer sending to USB (&out OUT_USB)
  3. Power on the keyboard via USB via a power bank
  4. Type, text should not show up anywhere ('consumed' by power bank)

Testing

  • Built and flashed the firmware with the commit changes, ran the same steps above, and text showed up on the expected device.
  • Tested using 2 laptops (Ubuntu / Windows) using 2 different power banks.
  • Tested for regression, keyboard still works as expected for waking up the host machine (CONFIG_USB_DEVICE_REMOTE_WAKEUP=y) and for boot protocol (CONFIG_ZMK_USB_BOOT=y).

Concerns

  • I can't find any USB or USB HID specifications that explicitly state that the host machine will do the USB configuration step before the USB HID connection is considered complete.
  • It's my first time contributing to ZMK, please let me know how the pull request can be improved!

@angweekiat angweekiat requested a review from a team as a code owner September 4, 2024 15:08
@Nick-Munnich Nick-Munnich added bug Something isn't working usb labels Sep 4, 2024
Copy link
Contributor

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

One major question about testing/statuses for another scenario.

app/src/usb.c Outdated Show resolved Hide resolved
Update `zmk_usb_get_conn_state` mapping to only treat
`USB_DC_CONFIGURED` and `USB_DC_RESUME` as succesful HID connection.

This is a naive solution for zmkfirmware#841,
where having a USB-power cable without data driving the keyboard, like a
power bank, still has the firmware thinking that it's connected to a
host and futilely send data over USB.

When the power cable is plugged in, the USB DC status (from Zephyr) goes
through the following states:
```
USB_DC_CONNECTED -> USB_DC_SUSPEND
```
While for data cables:
```
USB_DC_CONNECTED -> USB_DC_SUSPEND -> USB_DC_RESUME -> USB_DC_RESET -> USB_DC_CONFIGURED
```
The enum mapping changes do allow the firmware to differentiate the 2
cases, but I'm unable to find any USB or USB HID specifications that
explicitly states that the host machine will do the USB configuration
step before the USB HID connection is considered complete. Just for
safety sake, I added `USB_DC_RESUME` to be considered as successful too.

Steps to reproduce original issue
---------------------------------
1. (Setup) Have ZMK keyboard with bluetooth profile enabled, set to prefer sending to USB
2. Power on keyboard via USB via power bank
3. Type, text should not show up anywhere ('consumed' by power bank)

Testing
------
Built and flashed firmware with the commit changes, ran the same steps
above, text showed up on expected device.

Tested using 2 laptops (Ubuntu / Windows) using 2 different power banks.

Logs (before)
-----------------
```
--- Started keyboard via battery
[00:00:00.554,992] <inf> zmk: Welcome to ZMK!
...
[00:00:01.133,514] <dbg> zmk: zmk_usb_get_conn_state: state: 11

--- Plugged power bank to keyboard
[00:00:03.959,777] <inf> usb_hid: Device connected
[00:00:03.959,808] <dbg> zmk: zmk_usb_get_conn_state: state: 2
[00:00:03.959,838] <dbg> zmk: zmk_usb_get_conn_state: state: 2
[00:00:03.959,869] <dbg> zmk: get_selected_transport: Only BLE is ready.
[00:00:03.962,890] <inf> usb_hid: Device suspended
[00:00:03.962,921] <dbg> zmk: zmk_usb_get_conn_state: state: 5
[00:00:03.962,951] <dbg> zmk: zmk_usb_get_conn_state: state: 5
[00:00:03.962,982] <dbg> zmk: get_selected_transport: Both endpoint transports are ready. Using 0
[00:00:03.962,982] <dbg> zmk: zmk_endpoints_send_report: usage page 0x07
[00:00:03.963,012] <dbg> zmk: zmk_endpoints_send_report: usage page 0x0C
[00:00:03.963,073] <inf> zmk: Endpoint changed: USB
```

Logs with mapping changes (after)
---------------------------------
```
--- Started keyboard via battery
[00:00:00.547,943] <inf> zmk: Welcome to ZMK!
...
[00:00:00.549,804] <dbg> zmk: zmk_usb_get_conn_state: state: 11
...

--- Plugged power bank to keyboard
[00:00:03.686,065] <inf> usb_hid: Device connected
[00:00:03.686,126] <dbg> zmk: zmk_usb_get_conn_state: state: 2
[00:00:03.686,126] <dbg> zmk: zmk_usb_get_conn_state: state: 2
[00:00:03.686,157] <dbg> zmk: get_selected_transport: Only BLE is ready.
[00:00:03.689,208] <inf> usb_hid: Device suspended
[00:00:03.689,239] <dbg> zmk: zmk_usb_get_conn_state: state: 5
[00:00:03.689,270] <dbg> zmk: zmk_usb_get_conn_state: state: 5
[00:00:03.689,270] <dbg> zmk: get_selected_transport: Only BLE is ready.
```

Concerns
--------
- This should work for USB Boot Protocol Support, but I have yet to test it.
- TODO: Check what happens when keyboard that doesn't have bluetooth profiles has a power bank plugged into it
- Above test isn't comprehensive. Maybe this can be a config option instead to catch unknown cases?
@angweekiat angweekiat force-pushed the fix/usb-power-detection-ble-fallback branch from 3dc8662 to 78fdfa2 Compare September 14, 2024 11:03
Copy link
Contributor

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally had a chance to give this a test on a real device. Works like a champ from my few test scenarios. Thanks so much for the PR! I am going to suggest we leave some of our docs around this issue as-is for now, pending further real world testing with your change in place.

@petejohanson petejohanson merged commit 0adb80c into zmkfirmware:main Oct 14, 2024
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working usb
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants