samples: bluetooth: classic: add HID Device mouse sample - #109117
samples: bluetooth: classic: add HID Device mouse sample#109117chengkai15 wants to merge 1 commit into
Conversation
c86146f to
f2002de
Compare
f2002de to
eee586a
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new Bluetooth Classic HID Device sample that demonstrates a BR/EDR mouse peripheral. The application registers an SDP HID record with a standard mouse report descriptor, accepts incoming HID Host connections, and periodically sends input reports tracing a circular cursor path.
Changes:
- Adds the
hid_devicesample sources, project config, CMake build, sample.yaml, and README. - Implements the full
bt_hid_device_cbcallback set (connect, get/set report, get/set protocol, suspend, vc_unplug, intr data) and a complete HID SDP record including descriptor, language ID, and SSR attributes. - Drives a 10 Hz mouse report stream via a
k_timer+k_workusing a precomputed sine table, supporting both Boot and Report protocol modes.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/bluetooth/classic/hid_device/CMakeLists.txt | New CMake build for the sample. |
| samples/bluetooth/classic/hid_device/prj.conf | Enables BT Classic + HID Device, sets device name, CoD, page timeout. |
| samples/bluetooth/classic/hid_device/sample.yaml | Twister registration on qemu_cortex_m3 / qemu_x86. |
| samples/bluetooth/classic/hid_device/README.rst | Sample overview, requirements, build/run instructions. |
| samples/bluetooth/classic/hid_device/src/main.c | Application logic: SDP record, HID callbacks, mouse report timer/work. |
| char addr[BT_ADDR_LE_STR_LEN]; | ||
| struct bt_conn_info info; | ||
|
|
||
| bt_conn_get_info(conn, &info); | ||
| bt_addr_to_str(info.br.dst, addr, sizeof(addr)); |
| if (info.type != BT_CONN_TYPE_BR) { | ||
| return; | ||
| } | ||
|
|
| @@ -0,0 +1,8 @@ | |||
| #SPDX-License-Identifier: Apache-2.0 | |||
| FILE(GLOB app_sources src/*.c) | ||
| target_sources(app PRIVATE ${app_sources}) |
eee586a to
0fb4fca
Compare
|
Note: This PR depends on #94012 (Bluetooth HID Device profile) which introduces CONFIG_BT_HID_DEVICE and the bt_hid_device_* API. The following CI failures are expected until #94012 is merged:
All three will resolve automatically once the dependency PR lands. The sample builds and runs correctly when the HID Device profile code is present (verified locally on native_sim and with real hardware) |
|
|
This pull request has been marked as stale because it has been open (more than) 30 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 7 days. Note, that you can always re-open a closed pull request at any time. |
|
0fb4fca to
4d9107f
Compare
There was a problem hiding this comment.
🟢 Ready to approve
The changes are self-contained sample additions that match existing Bluetooth Classic sample patterns and no concrete issues were found in the modified content.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
3e7ac70 to
aac01ac
Compare
|
| static const uint8_t mouse_descriptor[] = { | ||
| HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), | ||
| HID_USAGE(HID_USAGE_GEN_DESKTOP_MOUSE), | ||
| HID_COLLECTION(HID_COLLECTION_APPLICATION), | ||
| HID_REPORT_ID(MOUSE_REPORT_ID), | ||
| HID_USAGE(HID_USAGE_GEN_DESKTOP_POINTER), | ||
| HID_COLLECTION(HID_COLLECTION_PHYSICAL), | ||
| HID_USAGE_PAGE(HID_USAGE_GEN_BUTTON), | ||
| HID_USAGE_MIN8(1), | ||
| HID_USAGE_MAX8(8), | ||
| HID_LOGICAL_MIN8(0), | ||
| HID_LOGICAL_MAX8(1), | ||
| HID_REPORT_COUNT(8), | ||
| HID_REPORT_SIZE(1), | ||
| HID_INPUT(0x02), | ||
| HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), | ||
| HID_USAGE(HID_USAGE_GEN_DESKTOP_X), | ||
| HID_USAGE(HID_USAGE_GEN_DESKTOP_Y), | ||
| HID_USAGE(HID_USAGE_GEN_DESKTOP_WHEEL), | ||
| HID_LOGICAL_MIN8(-127), | ||
| HID_LOGICAL_MAX8(127), | ||
| HID_REPORT_SIZE(8), | ||
| HID_REPORT_COUNT(3), | ||
| HID_INPUT(0x06), | ||
| HID_END_COLLECTION, | ||
| HID_END_COLLECTION, | ||
| }; |
There was a problem hiding this comment.
There is a helper macro HID_MOUSE_REPORT_DESC. Why not use it to define the mouse report descriptor?
There was a problem hiding this comment.
reuse HID_MOUSE_REPORT_DESC done
| static int hid_set_report_cb(struct bt_hid_device *hid, uint8_t type, struct net_buf *buf) | ||
| { | ||
| uint8_t report_id = 0; | ||
|
|
||
| ARG_UNUSED(hid); | ||
|
|
||
| /* The Report ID byte is only present when the report descriptor in use | ||
| * declares Report IDs, which the Boot Protocol reports do not. | ||
| */ | ||
| if (!hid_boot_mode) { | ||
| if (buf->len < sizeof(report_id)) { | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| report_id = net_buf_pull_u8(buf); | ||
|
|
||
| if (report_id != MOUSE_REPORT_ID) { | ||
| /* Mapped to ERR_INVALID_REPORT_ID by the stack */ | ||
| return -ENOENT; | ||
| } | ||
| } | ||
|
|
||
| /* A mouse has no state the host needs to push, so the payload is only | ||
| * logged here. A device with OUTPUT or FEATURE reports would apply it, | ||
| * and reject the types its descriptor does not declare. | ||
| */ | ||
| printk("Set Report: type %u id %u len %u\n", type, report_id, buf->len); | ||
|
|
||
| return 0; | ||
| } |
There was a problem hiding this comment.
Why not set the callback to NULL?
There was a problem hiding this comment.
set_report can't be NULL — SET_REPORT is a mandatory HID Device transaction (HID Profile spec v1.1.2), and bt_hid_device_register() rejects a NULL set_report with -EINVAL. A mouse has nothing to apply, so the callback just logs the payload and returns success.
| static void hid_output_report_cb(struct bt_hid_device *hid, struct net_buf *buf) | ||
| { | ||
| uint8_t report_id; | ||
|
|
||
| ARG_UNUSED(hid); | ||
|
|
||
| if (hid_boot_mode) { | ||
| printk("Output report: len %u\n", buf->len); | ||
| return; | ||
| } | ||
|
|
||
| if (buf->len < sizeof(report_id)) { | ||
| printk("Malformed output report (len %u)\n", buf->len); | ||
| return; | ||
| } | ||
|
|
||
| report_id = net_buf_pull_u8(buf); | ||
| printk("Output report: id %u len %u\n", report_id, buf->len); | ||
| } |
There was a problem hiding this comment.
Since it is a mouse, and output report is not included by report descriptor, why not set the output callback to NULL.
There was a problem hiding this comment.
Done — a mouse has no output report, so .output_report is left unset (NULL) in the callback struct
| struct bt_conn_info info; | ||
|
|
||
| if (bt_conn_get_info(conn, &info) != 0) { | ||
| return; | ||
| } | ||
|
|
||
| if (info.type != BT_CONN_TYPE_BR) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
I think this code block is useless since the Bluetooth host is initialized by the application and only br connectable is enabled.
There was a problem hiding this comment.
Removed — this block went away with the rewrite; the sample no longer keeps that state.
There was a problem hiding this comment.
I did not find any changes here.
updated
| struct bt_conn_info info; | ||
|
|
||
| if (bt_conn_get_info(conn, &info) != 0) { | ||
| return; | ||
| } | ||
|
|
||
| if (info.type != BT_CONN_TYPE_BR) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Removed — this block went away with the rewrite; the sample no longer keeps that state.
| struct bt_conn_info info; | ||
|
|
||
| if (bt_conn_get_info(conn, &info) != 0) { | ||
| return; | ||
| } | ||
|
|
||
| if (info.type != BT_CONN_TYPE_BR) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Removed — this block went away with the rewrite; the sample no longer keeps that state.
| static void send_mouse_report(void) | ||
| { | ||
| struct net_buf *buf; | ||
| int8_t dx, dy; | ||
| int err; | ||
|
|
||
| if (default_hid == NULL) { | ||
| return; | ||
| } | ||
|
|
||
| buf = bt_hid_device_create_pdu(&hid_tx_pool); | ||
| if (buf == NULL) { | ||
| printk("Failed to allocate HID PDU\n"); | ||
| return; | ||
| } | ||
|
|
||
| dx = sine_table[(mouse_step + (MOUSE_CIRCLE_STEPS / 4)) % MOUSE_CIRCLE_STEPS]; | ||
| dy = sine_table[mouse_step % MOUSE_CIRCLE_STEPS]; | ||
|
|
||
| /* Boot Protocol mouse report: buttons(1) + X(1) + Y(1), no Report ID. | ||
| * Report Protocol mouse report: Report ID(1) + buttons(1) + X(1) + | ||
| * Y(1) + wheel(1), matching mouse_descriptor above. | ||
| */ | ||
| if (hid_boot_mode) { | ||
| net_buf_add_u8(buf, 0x00); | ||
| net_buf_add_u8(buf, (uint8_t)dx); | ||
| net_buf_add_u8(buf, (uint8_t)dy); | ||
| } else { | ||
| net_buf_add_u8(buf, MOUSE_REPORT_ID); | ||
| net_buf_add_u8(buf, 0x00); | ||
| net_buf_add_u8(buf, (uint8_t)dx); | ||
| net_buf_add_u8(buf, (uint8_t)dy); | ||
| net_buf_add_u8(buf, 0x00); | ||
| } | ||
|
|
||
| err = bt_hid_device_input_report(default_hid, buf); | ||
| if (err != 0) { | ||
| printk("Failed to send input report (err %d)\n", err); | ||
| net_buf_unref(buf); | ||
| return; | ||
| } | ||
|
|
||
| mouse_step = (mouse_step + 1) % MOUSE_CIRCLE_STEPS; | ||
| } |
There was a problem hiding this comment.
Do you consider using the real activity to replace the simulated data? Since the simulated data has been used in shell hid command, if the same behaviour is used in this example, what is the different between shell hid and this changes? Is it really necessary?
You could refer to the USB HID mouse sample,
zephyr/samples/subsys/usb/hid-mouse/src/main.c
Lines 43 to 83 in 161f758
There was a problem hiding this comment.
updated with usb hid sample
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
samples/bluetooth/classic/hid_device/src/main.c:392
hid_set_report_cb()currently accepts any report type. Per HIDP, Set_Report applies to OUTPUT/FEATURE reports; treating other types as success can mislead the host. Consider rejecting non-OUTPUT/FEATURE types up front with -EINVAL.
static int hid_set_report_cb(struct bt_hid_device *hid, uint8_t type, struct net_buf *buf)
{
uint8_t report_id = 0;
ARG_UNUSED(hid);
samples/bluetooth/classic/hid_device/src/main.c:503
hid_set_protocol_cb()treats any unknown protocol value as "Report" and returns success. Since the host-provided value is untrusted, validate it (BOOT/REPORT only) and return -EINVAL for unsupported values.
printk("Set Protocol: %s\n", protocol == BT_HID_PROTOCOL_BOOT_MODE ? "Boot" : "Report");
hid_boot_mode = (protocol == BT_HID_PROTOCOL_BOOT_MODE);
return 0;
| err = bt_hid_device_input_report(default_hid, buf); | ||
| if (err != 0) { | ||
| printk("Failed to send input report (err %d)\n", err); | ||
| net_buf_unref(buf); | ||
| return; | ||
| } |
|
The description of PR is not aligned with change of commit. Such as |
aac01ac to
d22d400
Compare
| { | ||
| ARG_UNUSED(hid); | ||
|
|
||
| printk("HID %s\n", suspended ? "suspended" : "exit suspend"); |
There was a problem hiding this comment.
| printk("HID %s\n", suspended ? "suspended" : "exit suspend"); | |
| printk("HID Host %s\n", suspended ? "suspended" : "exit suspend"); |
| struct bt_conn_info info; | ||
|
|
||
| if (bt_conn_get_info(conn, &info) != 0) { | ||
| return; | ||
| } | ||
|
|
||
| if (info.type != BT_CONN_TYPE_BR) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
I did not find any changes here.
updated
Add a Bluetooth Classic HID Device sample that demonstrates a mouse peripheral. The sample registers an SDP HID service record with a standard two-button mouse report descriptor (buttons + X/Y + wheel, no Report ID), waits for an incoming HID Host connection, and turns board button events into mouse input reports on the interrupt channel. Features demonstrated: - HID Device callback registration and SDP service setup - Boot Protocol and Report Protocol mode support - Get_Report, Set_Report and Set_Protocol request handling - Event-driven input reports mapped from board buttons via the input subsystem (left/right click and relative X/Y motion) - Suspend/Exit-Suspend and Virtual Cable Unplug handling On native_sim the button GPIOs can be driven from an SDL window with the gpio-emul-sdl backend. Verified end-to-end with a real USB HCI dongle as the controller and a second dongle running a Bluetooth HID Host: connect/pair/encrypt, HID control + interrupt channels, a Get_Report round-trip, and each of the four button/axis events producing the expected input report on the host. Signed-off-by: Kai Cheng <chengkai@xiaomi.com> Assisted-by: Claude Opus 4.8 (1M context)
d22d400 to
b4e5b53
Compare



Summary
Add a Bluetooth Classic HID Device sample application that demonstrates a mouse peripheral using the HID Device profile API introduced in #94012.
Dependencies
This PR depends on #94012 (Bluetooth HID Device profile implementation) being merged first. The sample builds on top of the
bt_hid_device_*API introduced there.Test plan
qemu_cortex_m3(310/310 objects, 0 errors, 0 warnings)native_sim(279/279 objects, 0 errors, 0 warnings)native_sim: Android phone discovered "hid-mouse", paired successfully, and mouse cursor moved in a circle on the phone screenFiles
samples/bluetooth/classic/hid_device/CMakeLists.txtsamples/bluetooth/classic/hid_device/prj.confsamples/bluetooth/classic/hid_device/sample.yamlsamples/bluetooth/classic/hid_device/README.rstsamples/bluetooth/classic/hid_device/src/main.c