feat(keyboard): 添加蓝牙HID支持和电池状态事件

- 添加BLE相关模块:ble_adv_ctrl_module、ble_adv_uuid16、ble_bas_module、
  ble_hid_module
- 新增电池状态事件(bat_state_event)用于监控电池电量、充电状态
- 在多个事件中添加HID_TRANSPORT_BLE支持,包括hid_led_event、
  set_protocol_event等
- 更新配置文件prj.conf以启用蓝牙功能、HID服务和设置系统
- 修改电池模块以计算并报告电池SOC百分比
- 集成CAF设置加载器以管理蓝牙配对信息
This commit is contained in:
2026-04-10 19:28:20 +08:00
parent b9b7d342f5
commit 39d2962258
21 changed files with 1186 additions and 60 deletions

491
src/ble_hid_module.c Normal file
View File

@@ -0,0 +1,491 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#define MODULE ble_hid_module
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <caf/events/ble_common_event.h>
#include <bluetooth/services/hids.h>
#include <zephyr/logging/log.h>
#include "hid_led_event.h"
#include "hid_report_sent_event.h"
#include "hid_transport_state_event.h"
#include "hid_tx_report_event.h"
#include "keyboard_core.h"
#include "set_protocol_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define BLE_HID_KEYS_REPORT_ID 0x01
#define BLE_HID_CONSUMER_REPORT_ID 0x02
#define BLE_HID_KEYS_REPORT_IDX 0
#define BLE_HID_CONSUMER_REPORT_IDX 1
#define BLE_HID_KEYS_LED_REPORT_SIZE 1U
#define BASE_USB_HID_SPEC_VERSION 0x0101
struct in_flight_report {
bool active;
enum keyboard_report_type report_type;
uint16_t sequence;
};
BT_HIDS_DEF(hids_obj,
KEYBOARD_NKRO_REPORT_SIZE,
KEYBOARD_CONSUMER_REPORT_SIZE,
BLE_HID_KEYS_LED_REPORT_SIZE);
static struct bt_conn *active_conn;
static struct in_flight_report in_flight;
static enum keyboard_protocol_mode protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
static bool initialized;
static bool running;
static bool secured;
static bool keyboard_report_notify_enabled;
static bool consumer_report_notify_enabled;
static bool boot_keyboard_notify_enabled;
static const uint8_t hid_report_desc[] = {
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x06, /* Usage (Keyboard) */
0xA1, 0x01, /* Collection (Application) */
0x85, BLE_HID_KEYS_REPORT_ID,
0x05, 0x07, /* Usage Page (Keyboard/Keypad) */
0x19, 0xE0, /* Usage Minimum (0xE0) */
0x29, 0xE7, /* Usage Maximum (0xE7) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x08, /* Report Count (8) */
0x81, 0x02, /* Input (Data,Var,Abs) */
0x05, 0x07, /* Usage Page (Keyboard/Keypad) */
0x19, 0x00, /* Usage Minimum (0x00) */
0x2A, 0xDF, 0x00, /* Usage Maximum (0x00DF) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x96, 0xE0, 0x00, /* Report Count (224) */
0x81, 0x02, /* Input (Data,Var,Abs) */
0x85, BLE_HID_KEYS_REPORT_ID,
0x05, 0x08, /* Usage Page (LEDs) */
0x19, 0x01, /* Usage Minimum (1) */
0x29, 0x05, /* Usage Maximum (5) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x05, /* Report Count (5) */
0x91, 0x02, /* Output (Data,Var,Abs) */
0x75, 0x03, /* Report Size (3) */
0x95, 0x01, /* Report Count (1) */
0x91, 0x01, /* Output (Const,Array,Abs) */
0xC0, /* End Collection */
0x05, 0x0C, /* Usage Page (Consumer) */
0x09, 0x01, /* Usage (Consumer Control) */
0xA1, 0x01, /* Collection (Application) */
0x85, BLE_HID_CONSUMER_REPORT_ID,
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xFF, 0x03, /* Logical Maximum (1023) */
0x19, 0x00, /* Usage Minimum (0) */
0x2A, 0xFF, 0x03, /* Usage Maximum (1023) */
0x75, 0x10, /* Report Size (16) */
0x95, 0x01, /* Report Count (1) */
0x81, 0x00, /* Input (Data,Array,Abs) */
0xC0 /* End Collection */
};
static void submit_set_protocol_event(void)
{
struct set_protocol_event *event = new_set_protocol_event();
event->transport = HID_TRANSPORT_BLE;
event->protocol_mode = protocol_mode;
APP_EVENT_SUBMIT(event);
}
static void submit_hid_led_event(uint8_t led_bm)
{
struct hid_led_event *event = new_hid_led_event();
event->transport = HID_TRANSPORT_BLE;
event->led_bm = led_bm;
APP_EVENT_SUBMIT(event);
}
static void submit_transport_state_event(void)
{
struct hid_transport_state_event *event = new_hid_transport_state_event();
bool ready = running && secured && (active_conn != NULL);
event->transport = HID_TRANSPORT_BLE;
event->ready = ready;
event->protocol_mode = protocol_mode;
event->keys_ready = ready &&
((protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ?
boot_keyboard_notify_enabled :
keyboard_report_notify_enabled);
event->consumer_ready = ready &&
(protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT) &&
consumer_report_notify_enabled;
APP_EVENT_SUBMIT(event);
}
static void submit_hid_report_sent_event(enum keyboard_report_type report_type,
uint16_t sequence, bool error)
{
struct hid_report_sent_event *event = new_hid_report_sent_event();
event->transport = HID_TRANSPORT_BLE;
event->report_type = report_type;
event->sequence = sequence;
event->error = error;
APP_EVENT_SUBMIT(event);
}
static void input_report_notify_handler(uint8_t report_id, enum bt_hids_notify_evt evt)
{
bool enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED);
if (report_id == BLE_HID_KEYS_REPORT_ID) {
keyboard_report_notify_enabled = enabled;
} else if (report_id == BLE_HID_CONSUMER_REPORT_ID) {
consumer_report_notify_enabled = enabled;
}
submit_transport_state_event();
}
static void boot_keyboard_notify_handler(enum bt_hids_notify_evt evt)
{
boot_keyboard_notify_enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED);
submit_transport_state_event();
}
static void hid_report_complete_cb(struct bt_conn *conn, void *user_data)
{
ARG_UNUSED(conn);
ARG_UNUSED(user_data);
if (!in_flight.active) {
return;
}
submit_hid_report_sent_event(in_flight.report_type, in_flight.sequence, false);
in_flight.active = false;
}
static void keyboard_led_report_common(struct bt_hids_rep *rep, bool write)
{
if (!write || (rep->data == NULL) || (rep->size < BLE_HID_KEYS_LED_REPORT_SIZE)) {
return;
}
submit_hid_led_event(rep->data[0]);
}
static void keyboard_led_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
keyboard_led_report_common(rep, write);
}
static void boot_keyboard_led_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
keyboard_led_report_common(rep, write);
}
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:
protocol_mode = KEYBOARD_PROTOCOL_MODE_BOOT;
break;
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
break;
default:
return;
}
submit_set_protocol_event();
submit_transport_state_event();
}
static int module_init(void)
{
struct bt_hids_init_param hids_init_param = { 0 };
struct bt_hids_inp_rep *input_report;
struct bt_hids_outp_feat_rep *output_report;
hids_init_param.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;
hids_init_param.info.b_country_code = 0x00;
hids_init_param.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE;
hids_init_param.rep_map.data = hid_report_desc;
hids_init_param.rep_map.size = sizeof(hid_report_desc);
hids_init_param.pm_evt_handler = pm_evt_handler;
hids_init_param.is_kb = true;
hids_init_param.boot_kb_notif_handler = boot_keyboard_notify_handler;
hids_init_param.boot_kb_outp_rep_handler = boot_keyboard_led_report_handler;
input_report = &hids_init_param.inp_rep_group_init.reports[BLE_HID_KEYS_REPORT_IDX];
input_report->id = BLE_HID_KEYS_REPORT_ID;
input_report->size = KEYBOARD_NKRO_REPORT_SIZE;
input_report->handler_ext = input_report_notify_handler;
hids_init_param.inp_rep_group_init.cnt++;
input_report = &hids_init_param.inp_rep_group_init.reports[BLE_HID_CONSUMER_REPORT_IDX];
input_report->id = BLE_HID_CONSUMER_REPORT_ID;
input_report->size = KEYBOARD_CONSUMER_REPORT_SIZE;
input_report->handler_ext = input_report_notify_handler;
hids_init_param.inp_rep_group_init.cnt++;
output_report = &hids_init_param.outp_rep_group_init.reports[0];
output_report->id = BLE_HID_KEYS_REPORT_ID;
output_report->size = BLE_HID_KEYS_LED_REPORT_SIZE;
output_report->handler = keyboard_led_report_handler;
hids_init_param.outp_rep_group_init.cnt = 1U;
return bt_hids_init(&hids_obj, &hids_init_param);
}
static int module_start(void)
{
if (running) {
return 0;
}
running = true;
submit_transport_state_event();
return 0;
}
static void module_pause(void)
{
if (!running) {
return;
}
in_flight.active = false;
running = false;
submit_transport_state_event();
}
static void reset_connection_state(void)
{
active_conn = NULL;
secured = false;
keyboard_report_notify_enabled = false;
consumer_report_notify_enabled = false;
boot_keyboard_notify_enabled = false;
protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
in_flight.active = false;
}
static bool handle_ble_peer_event(const struct ble_peer_event *event)
{
int err;
switch (event->state) {
case PEER_STATE_CONNECTED:
if (active_conn != NULL) {
return false;
}
active_conn = event->id;
protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
submit_set_protocol_event();
err = bt_hids_connected(&hids_obj, event->id);
if (err) {
LOG_ERR("bt_hids_connected failed (%d)", err);
}
submit_transport_state_event();
return false;
case PEER_STATE_SECURED:
if (active_conn != event->id) {
return false;
}
secured = true;
submit_transport_state_event();
return false;
case PEER_STATE_DISCONNECTED:
if (active_conn != event->id) {
return false;
}
err = bt_hids_disconnected(&hids_obj, event->id);
if (err) {
LOG_WRN("bt_hids_disconnected failed (%d)", err);
}
reset_connection_state();
submit_transport_state_event();
return false;
default:
return false;
}
}
static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
{
int err;
if (!running || (event->transport != HID_TRANSPORT_BLE) || in_flight.active) {
return false;
}
if ((active_conn == NULL) || !secured) {
return false;
}
if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) {
if (event->protocol_mode != protocol_mode) {
LOG_WRN("Drop BLE keys report due to protocol mismatch");
return false;
}
in_flight.active = true;
in_flight.report_type = event->report_type;
in_flight.sequence = event->sequence;
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
err = bt_hids_boot_kb_inp_rep_send(&hids_obj,
active_conn,
event->dyndata.data,
(uint8_t)event->dyndata.size,
hid_report_complete_cb);
} else {
err = bt_hids_inp_rep_send(&hids_obj,
active_conn,
BLE_HID_KEYS_REPORT_IDX,
event->dyndata.data,
(uint8_t)event->dyndata.size,
hid_report_complete_cb);
}
if (err) {
in_flight.active = false;
LOG_WRN("BLE keyboard report submit failed (%d)", err);
submit_hid_report_sent_event(KEYBOARD_REPORT_TYPE_KEYS,
event->sequence, true);
}
return false;
}
if (event->report_type == KEYBOARD_REPORT_TYPE_CONSUMER) {
if (protocol_mode != KEYBOARD_PROTOCOL_MODE_REPORT) {
LOG_WRN("Drop BLE consumer report in boot mode");
return false;
}
in_flight.active = true;
in_flight.report_type = event->report_type;
in_flight.sequence = event->sequence;
err = bt_hids_inp_rep_send(&hids_obj,
active_conn,
BLE_HID_CONSUMER_REPORT_IDX,
event->dyndata.data,
(uint8_t)event->dyndata.size,
hid_report_complete_cb);
if (err) {
in_flight.active = false;
LOG_WRN("BLE consumer report submit failed (%d)", err);
submit_hid_report_sent_event(KEYBOARD_REPORT_TYPE_CONSUMER,
event->sequence, true);
}
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_hid_tx_report_event(aeh)) {
return handle_hid_tx_report_event(cast_hid_tx_report_event(aeh));
}
if (is_ble_peer_event(aeh)) {
return handle_ble_peer_event(cast_ble_peer_event(aeh));
}
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
int err;
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
if (!initialized) {
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
if (is_power_down_event(aeh)) {
if (initialized) {
module_pause();
module_set_state(MODULE_STATE_STANDBY);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (initialized) {
int err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_report_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);