feat(hids): 添加HID服务模块支持键盘和多媒体功能

- 新增hids_module.c实现蓝牙HID服务,支持键盘NKRO和Consumer控制
- 添加hid_report_descriptor.h定义统一的HID描述符,包括键盘、多媒体和RAW HID
- 在CMakeLists.txt中注册hids模块源文件
- 配置prj.conf启用蓝牙HID相关配置项,设置设备名称和外观
- 修改main.c移除启动LED效果,简化主函数逻辑
- 添加settings_loader_def.h确保模块依赖正确加载
- 配置pm_static.yml分配flash存储空间给mcuboot和settings
- 调整电源管理超时时间从20秒增加到300秒
- 启用MCUBOOT引导加载器支持
This commit is contained in:
2026-03-13 16:31:02 +08:00
parent b3516b988a
commit 05f4f117b0
8 changed files with 390 additions and 20 deletions

193
src/modules/hids_module.c Normal file
View File

@@ -0,0 +1,193 @@
#include <bluetooth/services/hids.h>
#include <app_event_manager.h>
#define MODULE hids
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include "hid_types.h"
#include "hid_report_descriptor.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define INPUT_REPORT_COUNT 2
#define OUTPUT_REPORT_COUNT 1
#define KEYBOARD_REPORT_LEN 30
#define CONSUMER_REPORT_LEN 2
#define KEYBOARD_LED_REPORT_LEN 1
/* 注册 HIDS 实例。此版本聚焦最小可用链路Boot + Report。 */
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
static struct bt_conn *active_conn;
static enum bt_hids_pm current_pm = BT_HIDS_PM_REPORT;
static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
{
ARG_UNUSED(conn);
switch (evt) {
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
current_pm = BT_HIDS_PM_BOOT;
LOG_INF("HIDS protocol: boot");
break;
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
current_pm = BT_HIDS_PM_REPORT;
LOG_INF("HIDS protocol: report");
break;
default:
break;
}
}
static void report_notify_handler(enum bt_hids_notify_evt evt)
{
ARG_UNUSED(evt);
}
static void boot_keyboard_notif_handler(enum bt_hids_notify_evt evt)
{
ARG_UNUSED(evt);
}
static void boot_keyboard_output_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
/* Basic boot protocol support: accept host LED writes and keep state locally. */
if (!write || !rep || (rep->size == 0) || !rep->data) {
return;
}
LOG_DBG("Boot KB out report 0x%02x", rep->data[0]);
}
static void keyboard_output_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
/*
* 该回调用于 Report 协议的键盘 LED 输出NumLock 等)。
* 这里仅做最小解析并暴露注册回调,具体业务(例如驱动指示灯)留给上层实现。
*/
if (!write || !rep || !rep->data || (rep->size < KEYBOARD_LED_REPORT_LEN)) {
return;
}
uint8_t leds = rep->data[0];
LOG_DBG("Report KB out report 0x%02x", leds);
/*
* 预留:后续在这里把 LED 输出转换为 CAF 事件(例如 NumLock 状态事件),
* 由上层模块消费并驱动板级指示灯。
*/
ARG_UNUSED(leds);
}
static int hids_service_init(void)
{
static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
struct bt_hids_init_param init_param = { 0 };
struct bt_hids_inp_rep *input_report = &init_param.inp_rep_group_init.reports[0];
struct bt_hids_outp_feat_rep *output_report = &init_param.outp_rep_group_init.reports[0];
init_param.info.bcd_hid = 0x0101;
init_param.info.b_country_code = 0x00;
init_param.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE;
init_param.rep_map.data = report_map;
init_param.rep_map.size = sizeof(report_map);
input_report[0].id = REPORT_ID_KEYBOARD;
input_report[0].size = KEYBOARD_REPORT_LEN;
input_report[0].handler = report_notify_handler;
input_report[1].id = REPORT_ID_CONSUMER;
input_report[1].size = CONSUMER_REPORT_LEN;
input_report[1].handler = report_notify_handler;
/*
* Report 协议键盘输出报告:
* 与 Report Map 中 REPORT_ID_KEYBOARD 下定义的 1 字节 LED Output 对齐。
*/
output_report[0].id = REPORT_ID_KEYBOARD;
output_report[0].size = KEYBOARD_LED_REPORT_LEN;
output_report[0].handler = keyboard_output_report_handler;
init_param.inp_rep_group_init.cnt = INPUT_REPORT_COUNT;
init_param.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
init_param.pm_evt_handler = pm_evt_handler;
init_param.is_kb = true;
init_param.boot_kb_notif_handler = boot_keyboard_notif_handler;
init_param.boot_kb_outp_rep_handler = boot_keyboard_output_report_handler;
return bt_hids_init(&hids_obj, &init_param);
}
static void handle_ble_peer_event(const struct ble_peer_event *event)
{
switch (event->state) {
case PEER_STATE_CONNECTED:
__ASSERT_NO_MSG(active_conn == NULL);
active_conn = event->id;
if (bt_hids_connected(&hids_obj, active_conn)) {
LOG_WRN("bt_hids_connected failed");
}
break;
case PEER_STATE_DISCONNECTED:
if (active_conn == event->id) {
if (bt_hids_disconnected(&hids_obj, active_conn)) {
LOG_WRN("bt_hids_disconnected failed");
}
active_conn = NULL;
}
break;
default:
break;
}
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
static bool initialized;
__ASSERT_NO_MSG(!initialized);
initialized = true;
if (hids_service_init()) {
LOG_ERR("Cannot initialize HIDS service");
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
if (is_ble_peer_event(aeh)) {
handle_ble_peer_event(cast_ble_peer_event(aeh));
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
/* Ensure GATT HIDS is registered before BLE is enabled by ble_state. */
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);