Skip to content

samples: bluetooth: classic: add HID Device mouse sample - #109117

Open
chengkai15 wants to merge 1 commit into
zephyrproject-rtos:mainfrom
chengkai15:hid_device_sample
Open

samples: bluetooth: classic: add HID Device mouse sample#109117
chengkai15 wants to merge 1 commit into
zephyrproject-rtos:mainfrom
chengkai15:hid_device_sample

Conversation

@chengkai15

@chengkai15 chengkai15 commented May 14, 2026

Copy link
Copy Markdown
Member

Summary

Add a Bluetooth Classic HID Device sample application that demonstrates a mouse peripheral using the HID Device profile API introduced in #94012.

  • Registers a full HID SDP service record with a standard mouse report descriptor (3 buttons + X/Y displacement + scroll wheel, with Report ID)
  • Waits for incoming HID Host connections as an acceptor (discoverable + connectable)
  • Sends periodic input reports (10 Hz) simulating circular cursor movement via a sine lookup table
  • Implements all HID Device callbacks: connected/disconnected, Get/Set Report, Get/Set Protocol, interrupt data, Virtual Cable Unplug, Suspend/Resume
  • Supports both Boot Protocol and Report Protocol modes

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

  • Build verified on qemu_cortex_m3 (310/310 objects, 0 errors, 0 warnings)
  • Build verified on native_sim (279/279 objects, 0 errors, 0 warnings)
  • End-to-end tested with a real USB HCI dongle (CSR BT 4.2) on native_sim: Android phone discovered "hid-mouse", paired successfully, and mouse cursor moved in a circle on the phone screen

Files

File Description
samples/bluetooth/classic/hid_device/CMakeLists.txt CMake build configuration
samples/bluetooth/classic/hid_device/prj.conf Kconfig: enables HID Device, sets CoD to pointing device
samples/bluetooth/classic/hid_device/sample.yaml Twister test registration
samples/bluetooth/classic/hid_device/README.rst Sample documentation
samples/bluetooth/classic/hid_device/src/main.c Main application (~570 lines)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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_device sample sources, project config, CMake build, sample.yaml, and README.
  • Implements the full bt_hid_device_cb callback 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_work using 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.

Comment on lines +503 to +507
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));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated

if (info.type != BT_CONN_TYPE_BR) {
return;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated

@@ -0,0 +1,8 @@
#SPDX-License-Identifier: Apache-2.0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated

Comment on lines +7 to +8
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated

@chengkai15
chengkai15 force-pushed the hid_device_sample branch from eee586a to 0fb4fca Compare May 14, 2026 09:30
@chengkai15

Copy link
Copy Markdown
Member Author

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:

  • Kconfig compliance: CONFIG_BT_HID_DEVICE reported as undefined symbol
  • Twister build: prj.conf references CONFIG_BT_HID_DEVICE which doesn't exist on main yet
  • Doc build: bt_hid_device doxygen group not found (header not present on main)

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)

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

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.

@github-actions github-actions Bot added the Stale label Jun 14, 2026
@github-actions github-actions Bot closed this Jun 21, 2026
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 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.

@github-actions github-actions Bot removed the Stale label Aug 5, 2026
@chengkai15
chengkai15 force-pushed the hid_device_sample branch 3 times, most recently from 3e7ac70 to aac01ac Compare August 5, 2026 03:26
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

Comment on lines +72 to +98
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,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There is a helper macro HID_MOUSE_REPORT_DESC. Why not use it to define the mouse report descriptor?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

reuse HID_MOUSE_REPORT_DESC done

Comment on lines +388 to +417
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not set the callback to NULL?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines +506 to +524
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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since it is a mouse, and output report is not included by report descriptor, why not set the output callback to NULL.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done — a mouse has no output report, so .output_report is left unset (NULL) in the callback struct

Comment on lines +571 to +579
struct bt_conn_info info;

if (bt_conn_get_info(conn, &info) != 0) {
return;
}

if (info.type != BT_CONN_TYPE_BR) {
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this code block is useless since the Bluetooth host is initialized by the application and only br connectable is enabled.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed — this block went away with the rewrite; the sample no longer keeps that state.

@lylezhu2012 lylezhu2012 Aug 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I did not find any changes here.

updated

Comment on lines +591 to +599
struct bt_conn_info info;

if (bt_conn_get_info(conn, &info) != 0) {
return;
}

if (info.type != BT_CONN_TYPE_BR) {
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ditto.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed — this block went away with the rewrite; the sample no longer keeps that state.

Comment on lines +607 to +615
struct bt_conn_info info;

if (bt_conn_get_info(conn, &info) != 0) {
return;
}

if (info.type != BT_CONN_TYPE_BR) {
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ditto.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed — this block went away with the rewrite; the sample no longer keeps that state.

Comment on lines +300 to +343
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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,

static void input_cb(struct input_event *evt, void *user_data)
{
static uint8_t tmp[MOUSE_REPORT_COUNT];
ARG_UNUSED(user_data);
switch (evt->code) {
case INPUT_KEY_0:
WRITE_BIT(tmp[MOUSE_BTN_REPORT_IDX], MOUSE_BTN_LEFT, evt->value);
break;
case INPUT_KEY_1:
WRITE_BIT(tmp[MOUSE_BTN_REPORT_IDX], MOUSE_BTN_RIGHT, evt->value);
break;
case INPUT_KEY_2:
if (evt->value) {
tmp[MOUSE_X_REPORT_IDX] += 10U;
}
break;
case INPUT_KEY_3:
if (evt->value) {
tmp[MOUSE_Y_REPORT_IDX] += 10U;
}
break;
default:
LOG_INF("Unrecognized input code %u value %d",
evt->code, evt->value);
return;
}
if (k_msgq_put(&mouse_msgq, tmp, K_NO_WAIT) != 0) {
LOG_ERR("Failed to put new input event");
}
tmp[MOUSE_X_REPORT_IDX] = 0U;
tmp[MOUSE_Y_REPORT_IDX] = 0U;
}
INPUT_CALLBACK_DEFINE(NULL, input_cb, NULL);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated with usb hid sample

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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;

Comment on lines +335 to +340
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;
}
@lylezhu2012

Copy link
Copy Markdown
Contributor

The description of PR is not aligned with change of commit. Such as samples/bluetooth/classic/hid_device/sample.yaml is not included by the commit.

{
ARG_UNUSED(hid);

printk("HID %s\n", suspended ? "suspended" : "exit suspend");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
printk("HID %s\n", suspended ? "suspended" : "exit suspend");
printk("HID Host %s\n", suspended ? "suspended" : "exit suspend");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated

Comment on lines +571 to +579
struct bt_conn_info info;

if (bt_conn_get_info(conn, &info) != 0) {
return;
}

if (info.type != BT_CONN_TYPE_BR) {
return;
}

@lylezhu2012 lylezhu2012 Aug 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants