feat: 更新键盘固件的事件系统和模块配置
- 在CMakeLists.txt中添加hid_boot_event.c、keyboard_led_event.c和ble_slot_ctrl_module.c源文件 - 新增Kconfig配置项NEW_KBD_BLE_BOND_ENABLE用于启用应用特定的BLE绑定支持 - 修改prj.conf配置,禁用配对模式下的设备名称广播功能 - 重构电池状态事件结构,将charging和full布尔字段改为flags位域,并提供相应的访问函数 - 添加hid_boot_event事件类型,用于处理HID Boot协议输入报告 - 重命名keyboard_led_state_event为keyboard_led_event并改进LED状态处理逻辑 - 移除hid_protocol_event中的transport字段,简化协议事件处理 - 分离hid_report_event和hid_boot_event,明确区分Report和Boot协议报文处理 - 重构battery_module.c代码结构,改用上下文结构体管理电池模块状态 - 更新ble_battery_module.c使用新的电池状态事件访问接口
This commit is contained in:
@@ -54,16 +54,28 @@ struct battery_status
|
||||
uint8_t soc;
|
||||
};
|
||||
|
||||
static struct k_work_delayable battery_sample_work;
|
||||
static int16_t battery_adc_sample_buffer;
|
||||
static atomic_t active;
|
||||
static struct battery_status last_status;
|
||||
static bool has_last_status;
|
||||
static int32_t battery_mv_window[BATTERY_MV_WINDOW_SIZE];
|
||||
static int64_t battery_mv_sum;
|
||||
static size_t battery_mv_count;
|
||||
static size_t battery_mv_index;
|
||||
static enum power_manager_level pm_restrict_level = POWER_MANAGER_LEVEL_MAX;
|
||||
struct battery_filter_state
|
||||
{
|
||||
int32_t window[BATTERY_MV_WINDOW_SIZE];
|
||||
int64_t sum;
|
||||
size_t count;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
struct battery_ctx
|
||||
{
|
||||
struct k_work_delayable sample_work;
|
||||
int16_t adc_sample_buffer;
|
||||
atomic_t active;
|
||||
struct battery_status last_status;
|
||||
bool has_last_status;
|
||||
struct battery_filter_state filter;
|
||||
enum power_manager_level pm_restrict_level;
|
||||
};
|
||||
|
||||
static struct battery_ctx battery = {
|
||||
.pm_restrict_level = POWER_MANAGER_LEVEL_MAX,
|
||||
};
|
||||
|
||||
static void battery_module_resume(void);
|
||||
|
||||
@@ -92,8 +104,8 @@ static int adc_sample_once_mv(int32_t *mv)
|
||||
return err;
|
||||
}
|
||||
|
||||
sequence.buffer = &battery_adc_sample_buffer;
|
||||
sequence.buffer_size = sizeof(battery_adc_sample_buffer);
|
||||
sequence.buffer = &battery.adc_sample_buffer;
|
||||
sequence.buffer_size = sizeof(battery.adc_sample_buffer);
|
||||
|
||||
err = adc_read_dt(&battery_adc, &sequence);
|
||||
if (err)
|
||||
@@ -102,12 +114,12 @@ static int adc_sample_once_mv(int32_t *mv)
|
||||
return err;
|
||||
}
|
||||
|
||||
*mv = battery_adc_sample_buffer;
|
||||
*mv = battery.adc_sample_buffer;
|
||||
err = adc_raw_to_millivolts_dt(&battery_adc, mv);
|
||||
if (err)
|
||||
{
|
||||
LOG_WRN("adc_raw_to_millivolts_dt failed (err=%d raw=%d)",
|
||||
err, battery_adc_sample_buffer);
|
||||
err, battery.adc_sample_buffer);
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -134,21 +146,21 @@ static int read_battery_mv(int32_t *mv)
|
||||
* 使用固定窗口平均抑制采样抖动。
|
||||
* 窗口未填满前按当前样本数求平均,填满后使用环形缓冲滚动更新。
|
||||
*/
|
||||
if (battery_mv_count < BATTERY_MV_WINDOW_SIZE)
|
||||
if (battery.filter.count < BATTERY_MV_WINDOW_SIZE)
|
||||
{
|
||||
battery_mv_window[battery_mv_index] = battery_mv;
|
||||
battery_mv_sum += battery_mv;
|
||||
battery_mv_count++;
|
||||
battery.filter.window[battery.filter.index] = battery_mv;
|
||||
battery.filter.sum += battery_mv;
|
||||
battery.filter.count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
battery_mv_sum -= battery_mv_window[battery_mv_index];
|
||||
battery_mv_window[battery_mv_index] = battery_mv;
|
||||
battery_mv_sum += battery_mv;
|
||||
battery.filter.sum -= battery.filter.window[battery.filter.index];
|
||||
battery.filter.window[battery.filter.index] = battery_mv;
|
||||
battery.filter.sum += battery_mv;
|
||||
}
|
||||
|
||||
battery_mv_index = (battery_mv_index + 1U) % BATTERY_MV_WINDOW_SIZE;
|
||||
*mv = (int32_t)(battery_mv_sum / (int64_t)battery_mv_count);
|
||||
battery.filter.index = (battery.filter.index + 1U) % BATTERY_MV_WINDOW_SIZE;
|
||||
*mv = (int32_t)(battery.filter.sum / (int64_t)battery.filter.count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -183,13 +195,15 @@ static int read_battery_status(struct battery_status *status)
|
||||
|
||||
static void publish_battery_status_event(const struct battery_status *status)
|
||||
{
|
||||
struct battery_status_event *event = new_battery_status_event();
|
||||
battery_status_event_submit(status->charging, status->full, status->soc);
|
||||
}
|
||||
|
||||
event->charging = status->charging;
|
||||
event->full = status->full;
|
||||
event->soc = status->soc;
|
||||
|
||||
APP_EVENT_SUBMIT(event);
|
||||
static bool battery_status_changed(const struct battery_status *lhs,
|
||||
const struct battery_status *rhs)
|
||||
{
|
||||
return (lhs->charging != rhs->charging) ||
|
||||
(lhs->full != rhs->full) ||
|
||||
(lhs->soc != rhs->soc);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -202,18 +216,33 @@ static void update_power_restrict_by_charging(bool charging)
|
||||
enum power_manager_level target = charging ?
|
||||
POWER_MANAGER_LEVEL_ALIVE : POWER_MANAGER_LEVEL_SUSPENDED;
|
||||
|
||||
if (pm_restrict_level == target)
|
||||
if (battery.pm_restrict_level == target)
|
||||
return;
|
||||
|
||||
pm_restrict_level = target;
|
||||
battery.pm_restrict_level = target;
|
||||
power_manager_restrict(MODULE_IDX(MODULE), target);
|
||||
}
|
||||
|
||||
static void battery_sampling_set_enabled(bool enable)
|
||||
{
|
||||
atomic_set(&battery.active, enable);
|
||||
(void)gpio_pin_set_dt(&battery_en_gpio, enable ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE);
|
||||
|
||||
if (enable)
|
||||
{
|
||||
k_work_reschedule(&battery.sample_work, K_NO_WAIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)k_work_cancel_delayable(&battery.sample_work);
|
||||
}
|
||||
}
|
||||
|
||||
static void battery_sample_fn(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
|
||||
if (!atomic_get(&active))
|
||||
if (!atomic_get(&battery.active))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -232,18 +261,16 @@ static void battery_sample_fn(struct k_work *work)
|
||||
* 仅在状态发生变化时上报,避免重复事件淹没总线。
|
||||
* 变化条件:充电标志、满电标志、SOC 任意一个变化。
|
||||
*/
|
||||
if (!has_last_status ||
|
||||
(sampled.charging != last_status.charging) ||
|
||||
(sampled.full != last_status.full) ||
|
||||
(sampled.soc != last_status.soc))
|
||||
if (!battery.has_last_status ||
|
||||
battery_status_changed(&sampled, &battery.last_status))
|
||||
{
|
||||
last_status = sampled;
|
||||
has_last_status = true;
|
||||
battery.last_status = sampled;
|
||||
battery.has_last_status = true;
|
||||
publish_battery_status_event(&sampled);
|
||||
}
|
||||
|
||||
out_reschedule:
|
||||
k_work_reschedule(&battery_sample_work, K_MSEC(BATTERY_SAMPLE_INTERVAL_MS));
|
||||
k_work_reschedule(&battery.sample_work, K_MSEC(BATTERY_SAMPLE_INTERVAL_MS));
|
||||
}
|
||||
|
||||
static int battery_module_init(void)
|
||||
@@ -283,44 +310,38 @@ static int battery_module_init(void)
|
||||
/* 默认非充电态允许进入 SUSPENDED,但禁止进入 OFF。 */
|
||||
update_power_restrict_by_charging(false);
|
||||
|
||||
k_work_init_delayable(&battery_sample_work, battery_sample_fn);
|
||||
has_last_status = false;
|
||||
battery_mv_sum = 0;
|
||||
battery_mv_count = 0;
|
||||
battery_mv_index = 0;
|
||||
atomic_set(&active, false);
|
||||
k_work_init_delayable(&battery.sample_work, battery_sample_fn);
|
||||
battery.has_last_status = false;
|
||||
battery.filter.sum = 0;
|
||||
battery.filter.count = 0;
|
||||
battery.filter.index = 0;
|
||||
atomic_set(&battery.active, false);
|
||||
|
||||
atomic_set(&active, true);
|
||||
(void)gpio_pin_set_dt(&battery_en_gpio, 1);
|
||||
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
|
||||
battery_sampling_set_enabled(true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void battery_module_suspend(void)
|
||||
{
|
||||
if (!atomic_get(&active))
|
||||
if (!atomic_get(&battery.active))
|
||||
{
|
||||
/* 已经处于挂起态,避免重复上报 STANDBY 造成 power_down 循环。 */
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_set(&active, false);
|
||||
(void)k_work_cancel_delayable(&battery_sample_work);
|
||||
(void)gpio_pin_set_dt(&battery_en_gpio, 0);
|
||||
battery_sampling_set_enabled(false);
|
||||
module_set_state(MODULE_STATE_STANDBY);
|
||||
}
|
||||
|
||||
static void battery_module_resume(void)
|
||||
{
|
||||
if (atomic_get(&active))
|
||||
if (atomic_get(&battery.active))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_set(&active, true);
|
||||
(void)gpio_pin_set_dt(&battery_en_gpio, 1);
|
||||
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
|
||||
battery_sampling_set_enabled(true);
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ BT_GATT_SERVICE_DEFINE(ble_battery_svc,
|
||||
|
||||
static bool handle_battery_status_event(const struct battery_status_event *event)
|
||||
{
|
||||
battery_level = event->soc;
|
||||
battery_level = battery_status_event_get_soc(event);
|
||||
|
||||
if (!notify_enabled) {
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -28,33 +29,45 @@ enum ble_bond_cfg_opt {
|
||||
#define PEER_ID_KEY "peer_id"
|
||||
#define BT_LUT_KEY "bt_lut"
|
||||
|
||||
enum state {
|
||||
STATE_DISABLED,
|
||||
STATE_IDLE,
|
||||
STATE_STANDBY,
|
||||
enum ble_bond_state {
|
||||
BLE_BOND_STATE_DISABLED,
|
||||
BLE_BOND_STATE_IDLE,
|
||||
BLE_BOND_STATE_STANDBY,
|
||||
};
|
||||
|
||||
BUILD_ASSERT(CONFIG_BT_ID_MAX >= 2, "Need at least one resettable identity");
|
||||
|
||||
#define APP_PEER_COUNT (CONFIG_BT_ID_MAX - 1)
|
||||
#define BLE_BOND_SLOT_COUNT 3
|
||||
|
||||
static enum state state = STATE_DISABLED;
|
||||
BUILD_ASSERT(BLE_BOND_SLOT_COUNT <= APP_PEER_COUNT,
|
||||
"BLE slot count exceeds available Bluetooth identities");
|
||||
|
||||
/* app peer id -> bt stack id mapping (default identity 0 is not used). */
|
||||
static uint8_t bt_stack_id_lut[APP_PEER_COUNT];
|
||||
static bool bt_stack_id_lut_valid;
|
||||
struct ble_bond_storage {
|
||||
uint8_t bt_stack_id_lut[APP_PEER_COUNT];
|
||||
bool bt_stack_id_lut_valid;
|
||||
uint8_t cur_peer_id;
|
||||
bool cur_peer_id_valid;
|
||||
};
|
||||
|
||||
static uint8_t cur_ble_peer_id;
|
||||
static bool cur_peer_id_valid;
|
||||
struct ble_bond_ctx {
|
||||
enum ble_bond_state state;
|
||||
struct ble_bond_storage storage;
|
||||
bool auto_switch_in_progress;
|
||||
};
|
||||
|
||||
static const char *state_name(enum state s)
|
||||
static struct ble_bond_ctx bond = {
|
||||
.state = BLE_BOND_STATE_DISABLED,
|
||||
};
|
||||
|
||||
static const char *state_name(enum ble_bond_state s)
|
||||
{
|
||||
switch (s) {
|
||||
case STATE_DISABLED:
|
||||
case BLE_BOND_STATE_DISABLED:
|
||||
return "DISABLED";
|
||||
case STATE_IDLE:
|
||||
case BLE_BOND_STATE_IDLE:
|
||||
return "IDLE";
|
||||
case STATE_STANDBY:
|
||||
case BLE_BOND_STATE_STANDBY:
|
||||
return "STANDBY";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
@@ -63,8 +76,8 @@ static const char *state_name(enum state s)
|
||||
|
||||
static uint8_t get_bt_stack_peer_id(uint8_t app_id)
|
||||
{
|
||||
__ASSERT_NO_MSG(app_id < APP_PEER_COUNT);
|
||||
return bt_stack_id_lut[app_id];
|
||||
__ASSERT_NO_MSG(app_id < BLE_BOND_SLOT_COUNT);
|
||||
return bond.storage.bt_stack_id_lut[app_id];
|
||||
}
|
||||
|
||||
static int store_peer_id(uint8_t peer_id)
|
||||
@@ -78,7 +91,9 @@ static int store_bt_stack_id_lut(void)
|
||||
{
|
||||
char key[] = MODULE_NAME "/" BT_LUT_KEY;
|
||||
|
||||
return settings_save_one(key, bt_stack_id_lut, sizeof(bt_stack_id_lut));
|
||||
return settings_save_one(key,
|
||||
bond.storage.bt_stack_id_lut,
|
||||
sizeof(bond.storage.bt_stack_id_lut));
|
||||
}
|
||||
|
||||
static void submit_peer_op_event(enum peer_operation op, uint8_t app_id)
|
||||
@@ -93,31 +108,31 @@ static void submit_peer_op_event(enum peer_operation op, uint8_t app_id)
|
||||
|
||||
static void init_bt_stack_id_lut(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bt_stack_id_lut); i++) {
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bond.storage.bt_stack_id_lut); i++) {
|
||||
/* Keep id 0 (BT_ID_DEFAULT) untouched for safe reset/unpair flow. */
|
||||
bt_stack_id_lut[i] = i + 1;
|
||||
bond.storage.bt_stack_id_lut[i] = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static bool storage_data_is_valid(void)
|
||||
{
|
||||
if (!cur_peer_id_valid || !bt_stack_id_lut_valid) {
|
||||
if (!bond.storage.cur_peer_id_valid || !bond.storage.bt_stack_id_lut_valid) {
|
||||
LOG_WRN("Stored data invalid: peer_valid=%d lut_valid=%d",
|
||||
cur_peer_id_valid, bt_stack_id_lut_valid);
|
||||
bond.storage.cur_peer_id_valid, bond.storage.bt_stack_id_lut_valid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cur_ble_peer_id >= APP_PEER_COUNT) {
|
||||
if (bond.storage.cur_peer_id >= BLE_BOND_SLOT_COUNT) {
|
||||
LOG_WRN("Stored peer id out of range: peer_id=%u max=%u",
|
||||
cur_ble_peer_id, APP_PEER_COUNT - 1);
|
||||
bond.storage.cur_peer_id, BLE_BOND_SLOT_COUNT - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bt_stack_id_lut); i++) {
|
||||
if ((bt_stack_id_lut[i] == BT_ID_DEFAULT) ||
|
||||
(bt_stack_id_lut[i] >= CONFIG_BT_ID_MAX)) {
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bond.storage.bt_stack_id_lut); i++) {
|
||||
if ((bond.storage.bt_stack_id_lut[i] == BT_ID_DEFAULT) ||
|
||||
(bond.storage.bt_stack_id_lut[i] >= CONFIG_BT_ID_MAX)) {
|
||||
LOG_WRN("Stored LUT invalid at idx=%u value=%u",
|
||||
(uint32_t)i, bt_stack_id_lut[i]);
|
||||
(uint32_t)i, bond.storage.bt_stack_id_lut[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -131,29 +146,31 @@ static int settings_set(const char *key, size_t len_rd,
|
||||
ssize_t rc;
|
||||
|
||||
if (!strcmp(key, PEER_ID_KEY)) {
|
||||
if (len_rd != sizeof(cur_ble_peer_id)) {
|
||||
if (len_rd != sizeof(bond.storage.cur_peer_id)) {
|
||||
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
|
||||
PEER_ID_KEY, (uint32_t)len_rd, sizeof(cur_ble_peer_id));
|
||||
cur_peer_id_valid = false;
|
||||
PEER_ID_KEY, (uint32_t)len_rd, sizeof(bond.storage.cur_peer_id));
|
||||
bond.storage.cur_peer_id_valid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = read_cb(cb_arg, &cur_ble_peer_id, sizeof(cur_ble_peer_id));
|
||||
cur_peer_id_valid = (rc == sizeof(cur_ble_peer_id));
|
||||
if (!cur_peer_id_valid) {
|
||||
rc = read_cb(cb_arg, &bond.storage.cur_peer_id, sizeof(bond.storage.cur_peer_id));
|
||||
bond.storage.cur_peer_id_valid = (rc == sizeof(bond.storage.cur_peer_id));
|
||||
if (!bond.storage.cur_peer_id_valid) {
|
||||
LOG_WRN("Settings '%s' read failed: rc=%d", PEER_ID_KEY, (int)rc);
|
||||
}
|
||||
} else if (!strcmp(key, BT_LUT_KEY)) {
|
||||
if (len_rd != sizeof(bt_stack_id_lut)) {
|
||||
if (len_rd != sizeof(bond.storage.bt_stack_id_lut)) {
|
||||
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
|
||||
BT_LUT_KEY, (uint32_t)len_rd, sizeof(bt_stack_id_lut));
|
||||
bt_stack_id_lut_valid = false;
|
||||
BT_LUT_KEY, (uint32_t)len_rd, sizeof(bond.storage.bt_stack_id_lut));
|
||||
bond.storage.bt_stack_id_lut_valid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = read_cb(cb_arg, bt_stack_id_lut, sizeof(bt_stack_id_lut));
|
||||
bt_stack_id_lut_valid = (rc == sizeof(bt_stack_id_lut));
|
||||
if (!bt_stack_id_lut_valid) {
|
||||
rc = read_cb(cb_arg,
|
||||
bond.storage.bt_stack_id_lut,
|
||||
sizeof(bond.storage.bt_stack_id_lut));
|
||||
bond.storage.bt_stack_id_lut_valid = (rc == sizeof(bond.storage.bt_stack_id_lut));
|
||||
if (!bond.storage.bt_stack_id_lut_valid) {
|
||||
LOG_WRN("Settings '%s' read failed: rc=%d", BT_LUT_KEY, (int)rc);
|
||||
}
|
||||
}
|
||||
@@ -183,6 +200,164 @@ static int load_identities(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void disconnect_le_conn_cb(struct bt_conn *conn, void *user_data)
|
||||
{
|
||||
(void)user_data;
|
||||
|
||||
int err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
|
||||
if (!err) {
|
||||
LOG_INF("Disconnect LE peer for slot switch");
|
||||
} else if (err == -ENOTCONN) {
|
||||
LOG_INF("LE peer already disconnected during slot switch");
|
||||
} else {
|
||||
LOG_WRN("Failed to disconnect LE peer for slot switch err=%d", err);
|
||||
}
|
||||
}
|
||||
|
||||
struct peer_bond_lookup {
|
||||
const bt_addr_le_t *peer_addr;
|
||||
bool found;
|
||||
};
|
||||
|
||||
static void peer_bond_lookup_cb(const struct bt_bond_info *info, void *user_data)
|
||||
{
|
||||
struct peer_bond_lookup *lookup = user_data;
|
||||
|
||||
if (!bt_addr_le_cmp(&info->addr, lookup->peer_addr)) {
|
||||
lookup->found = true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool slot_has_peer_bond(uint8_t app_id, const bt_addr_le_t *peer_addr)
|
||||
{
|
||||
struct peer_bond_lookup lookup = {
|
||||
.peer_addr = peer_addr,
|
||||
};
|
||||
|
||||
bt_foreach_bond(get_bt_stack_peer_id(app_id), peer_bond_lookup_cb, &lookup);
|
||||
|
||||
return lookup.found;
|
||||
}
|
||||
|
||||
static bool find_peer_owner_slot(const bt_addr_le_t *peer_addr, uint8_t *owner_slot)
|
||||
{
|
||||
for (uint8_t slot = 0; slot < BLE_BOND_SLOT_COUNT; slot++) {
|
||||
if (slot_has_peer_bond(slot, peer_addr)) {
|
||||
*owner_slot = slot;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int select_peer(uint8_t peer_id)
|
||||
{
|
||||
if (peer_id >= BLE_BOND_SLOT_COUNT) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint8_t previous_peer_id = bond.storage.cur_peer_id;
|
||||
|
||||
if (bond.storage.cur_peer_id_valid && (previous_peer_id == peer_id)) {
|
||||
LOG_INF("Peer slot already selected: slot=%u stack_id=%u",
|
||||
peer_id, get_bt_stack_peer_id(peer_id));
|
||||
return 0;
|
||||
}
|
||||
|
||||
bond.storage.cur_peer_id = peer_id;
|
||||
bond.storage.cur_peer_id_valid = true;
|
||||
|
||||
int err = store_peer_id(bond.storage.cur_peer_id);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Failed to store peer_id=%u (err:%d)", bond.storage.cur_peer_id, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, bond.storage.cur_peer_id);
|
||||
bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_le_conn_cb, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool handle_ble_peer_event(const struct ble_peer_event *event)
|
||||
{
|
||||
if (event->state == PEER_STATE_CONNECTED) {
|
||||
const bt_addr_le_t *peer_addr = bt_conn_get_dst(event->id);
|
||||
uint8_t owner_slot;
|
||||
|
||||
if (!peer_addr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (find_peer_owner_slot(peer_addr, &owner_slot) &&
|
||||
(owner_slot != bond.storage.cur_peer_id)) {
|
||||
char addr_str[BT_ADDR_LE_STR_LEN];
|
||||
int err;
|
||||
|
||||
bt_addr_le_to_str(peer_addr, addr_str, sizeof(addr_str));
|
||||
LOG_INF("Peer %s belongs to slot=%u, auto-switch from slot=%u",
|
||||
addr_str, owner_slot, bond.storage.cur_peer_id);
|
||||
|
||||
bond.auto_switch_in_progress = true;
|
||||
err = select_peer(owner_slot);
|
||||
if (err) {
|
||||
bond.auto_switch_in_progress = false;
|
||||
LOG_ERR("Auto-switch to slot=%u failed err=%d",
|
||||
owner_slot, err);
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->state == PEER_STATE_DISCONNECTED) {
|
||||
if (bond.auto_switch_in_progress) {
|
||||
bond.auto_switch_in_progress = false;
|
||||
LOG_INF("Auto-switch disconnect complete, waiting for reconnect on slot=%u",
|
||||
bond.storage.cur_peer_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->state != PEER_STATE_SECURED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct bt_conn_info info;
|
||||
int err = bt_conn_get_info(event->id, &info);
|
||||
|
||||
if (err) {
|
||||
LOG_ERR("Cannot get conn info for secured peer err=%d", err);
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t expected_stack_id = get_bt_stack_peer_id(bond.storage.cur_peer_id);
|
||||
|
||||
if (info.id == expected_stack_id) {
|
||||
LOG_INF("Secured peer matches selected slot=%u stack_id=%u",
|
||||
bond.storage.cur_peer_id,
|
||||
expected_stack_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INF("Disconnect peer on old id=%u expected=%u selected_slot=%u",
|
||||
info.id,
|
||||
expected_stack_id,
|
||||
bond.storage.cur_peer_id);
|
||||
|
||||
err = bt_conn_disconnect(event->id, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
if (err && (err != -ENOTCONN)) {
|
||||
LOG_ERR("Cannot disconnect peer on old id err=%d", err);
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int erase_peer(uint8_t app_id)
|
||||
{
|
||||
uint8_t stack_id = get_bt_stack_peer_id(app_id);
|
||||
@@ -209,7 +384,7 @@ static int erase_peer(uint8_t app_id)
|
||||
|
||||
static int erase_all_peers(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < APP_PEER_COUNT; i++) {
|
||||
for (uint8_t i = 0; i < BLE_BOND_SLOT_COUNT; i++) {
|
||||
int err = erase_peer(i);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -244,18 +419,14 @@ static bool handle_config_event(const struct config_event *event)
|
||||
case BLE_BOND_CFG_PEER_SELECT:
|
||||
if (event->dyndata.size >= 1) {
|
||||
uint8_t peer_id = event->dyndata.data[0];
|
||||
if (peer_id < APP_PEER_COUNT) {
|
||||
cur_ble_peer_id = peer_id;
|
||||
if (!store_peer_id(cur_ble_peer_id)) {
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
if (!select_peer(peer_id)) {
|
||||
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_BOND_CFG_PEER_ERASE:
|
||||
if (!erase_peer(cur_ble_peer_id)) {
|
||||
if (!erase_peer(bond.storage.cur_peer_id)) {
|
||||
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
break;
|
||||
@@ -280,7 +451,7 @@ static bool handle_config_event(const struct config_event *event)
|
||||
rsp_data->event_id = event->event_id;
|
||||
rsp_data->recipient = event->recipient;
|
||||
rsp_data->status = CONFIG_STATUS_SUCCESS;
|
||||
rsp_data->dyndata.data[0] = cur_ble_peer_id;
|
||||
rsp_data->dyndata.data[0] = bond.storage.cur_peer_id;
|
||||
APP_EVENT_SUBMIT(rsp_data);
|
||||
return true;
|
||||
}
|
||||
@@ -300,12 +471,15 @@ static int init_after_settings_loaded(void)
|
||||
|
||||
if (!storage_data_is_valid()) {
|
||||
LOG_WRN("Stored BLE bond data invalid, reinitializing defaults");
|
||||
cur_ble_peer_id = 0;
|
||||
bond.storage.cur_peer_id = 0;
|
||||
bond.storage.cur_peer_id_valid = true;
|
||||
init_bt_stack_id_lut();
|
||||
bond.storage.bt_stack_id_lut_valid = true;
|
||||
|
||||
err = store_peer_id(cur_ble_peer_id);
|
||||
err = store_peer_id(bond.storage.cur_peer_id);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to store peer_id=%u (err:%d)", cur_ble_peer_id, err);
|
||||
LOG_ERR("Failed to store peer_id=%u (err:%d)",
|
||||
bond.storage.cur_peer_id, err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
@@ -316,10 +490,12 @@ static int init_after_settings_loaded(void)
|
||||
}
|
||||
}
|
||||
|
||||
state = STATE_IDLE;
|
||||
bond.state = BLE_BOND_STATE_IDLE;
|
||||
LOG_INF("ble_bond init done: state=%s peer_id=%u stack_id=%u",
|
||||
state_name(state), cur_ble_peer_id, get_bt_stack_peer_id(cur_ble_peer_id));
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||
state_name(bond.state),
|
||||
bond.storage.cur_peer_id,
|
||||
get_bt_stack_peer_id(bond.storage.cur_peer_id));
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, bond.storage.cur_peer_id);
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
|
||||
return 0;
|
||||
@@ -331,12 +507,12 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||
|
||||
if (check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY) &&
|
||||
(state == STATE_DISABLED)) {
|
||||
(bond.state == BLE_BOND_STATE_DISABLED)) {
|
||||
LOG_INF("settings_loader ready, starting ble_bond init");
|
||||
int err = init_after_settings_loaded();
|
||||
if (err) {
|
||||
LOG_ERR("ble_bond init failed (err:%d), state=%s",
|
||||
err, state_name(state));
|
||||
err, state_name(bond.state));
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -345,22 +521,26 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
if (state == STATE_IDLE) {
|
||||
state = STATE_STANDBY;
|
||||
if (bond.state == BLE_BOND_STATE_IDLE) {
|
||||
bond.state = BLE_BOND_STATE_STANDBY;
|
||||
module_set_state(MODULE_STATE_OFF);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_wake_up_event(aeh)) {
|
||||
if (state == STATE_STANDBY) {
|
||||
state = STATE_IDLE;
|
||||
if (bond.state == BLE_BOND_STATE_STANDBY) {
|
||||
bond.state = BLE_BOND_STATE_IDLE;
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, bond.storage.cur_peer_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_ble_peer_event(aeh)) {
|
||||
return handle_ble_peer_event(cast_ble_peer_event(aeh));
|
||||
}
|
||||
|
||||
if (is_config_event(aeh)) {
|
||||
return handle_config_event(cast_config_event(aeh));
|
||||
}
|
||||
@@ -373,4 +553,5 @@ APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, power_down_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, config_event);
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
#include <caf/events/ble_common_event.h>
|
||||
|
||||
#include "hid_protocol_event.h"
|
||||
#include "hid_boot_event.h"
|
||||
#include "hid_report_event.h"
|
||||
#include "hid_report_descriptor.h"
|
||||
#include "keyboard_led_state_event.h"
|
||||
#include "keyboard_led_event.h"
|
||||
#include "mode_event.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
@@ -25,43 +26,66 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
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 bool ble_mode_selected;
|
||||
static bool num_lock_known;
|
||||
static bool num_lock_on;
|
||||
struct ble_hid_link {
|
||||
struct bt_conn *conn;
|
||||
enum bt_hids_pm protocol_mode;
|
||||
};
|
||||
|
||||
static enum hid_protocol_type pm_to_protocol(enum bt_hids_pm pm)
|
||||
struct ble_hid_policy {
|
||||
bool ble_mode_selected;
|
||||
};
|
||||
|
||||
struct ble_hid_led_state {
|
||||
bool valid;
|
||||
uint8_t led_mask;
|
||||
};
|
||||
|
||||
struct ble_hid_ctx {
|
||||
struct ble_hid_link link;
|
||||
struct ble_hid_policy policy;
|
||||
struct ble_hid_led_state led;
|
||||
};
|
||||
|
||||
static struct ble_hid_ctx ble_hid = {
|
||||
.link.protocol_mode = BT_HIDS_PM_REPORT,
|
||||
};
|
||||
|
||||
static bool ble_hid_is_connected(void)
|
||||
{
|
||||
return (pm == BT_HIDS_PM_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT;
|
||||
return ble_hid.link.conn != NULL;
|
||||
}
|
||||
|
||||
static bool ble_hid_is_active(void)
|
||||
{
|
||||
return ble_hid.policy.ble_mode_selected && ble_hid_is_connected();
|
||||
}
|
||||
|
||||
static bool ble_hid_is_boot_mode(void)
|
||||
{
|
||||
return ble_hid.link.protocol_mode == BT_HIDS_PM_BOOT;
|
||||
}
|
||||
|
||||
static bool ble_hid_is_report_mode(void)
|
||||
{
|
||||
return ble_hid.link.protocol_mode == BT_HIDS_PM_REPORT;
|
||||
}
|
||||
|
||||
static void publish_hid_protocol_event(enum hid_protocol_type protocol)
|
||||
{
|
||||
struct hid_protocol_event *event = new_hid_protocol_event();
|
||||
|
||||
event->transport = HID_TRANSPORT_BLE;
|
||||
event->protocol = protocol;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
hid_protocol_event_submit(protocol);
|
||||
}
|
||||
|
||||
/* BLE 输出报告的 bit0 对应 Num Lock。仅在状态变化时上报,避免重复通知。 */
|
||||
/* 主机 LED 输出报告变化时才上报,避免重复事件淹没总线。 */
|
||||
static void publish_num_lock_state_from_led_mask(uint8_t led_mask)
|
||||
{
|
||||
bool new_num_lock = (led_mask & BIT(0)) != 0U;
|
||||
|
||||
if (num_lock_known && (num_lock_on == new_num_lock)) {
|
||||
if (ble_hid.led.valid && (ble_hid.led.led_mask == led_mask)) {
|
||||
return;
|
||||
}
|
||||
|
||||
num_lock_known = true;
|
||||
num_lock_on = new_num_lock;
|
||||
ble_hid.led.valid = true;
|
||||
ble_hid.led.led_mask = led_mask;
|
||||
|
||||
struct keyboard_led_state_event *event = new_keyboard_led_state_event();
|
||||
|
||||
event->led_mask = led_mask;
|
||||
event->num_lock = new_num_lock;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
keyboard_led_event_submit(led_mask);
|
||||
}
|
||||
|
||||
static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
|
||||
@@ -70,16 +94,16 @@ static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
|
||||
|
||||
switch (evt) {
|
||||
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
|
||||
current_pm = BT_HIDS_PM_BOOT;
|
||||
ble_hid.link.protocol_mode = BT_HIDS_PM_BOOT;
|
||||
LOG_INF("HIDS protocol: boot");
|
||||
if (active_conn) {
|
||||
if (ble_hid_is_connected()) {
|
||||
publish_hid_protocol_event(HID_PROTO_BOOT);
|
||||
}
|
||||
break;
|
||||
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
|
||||
current_pm = BT_HIDS_PM_REPORT;
|
||||
ble_hid.link.protocol_mode = BT_HIDS_PM_REPORT;
|
||||
LOG_INF("HIDS protocol: report");
|
||||
if (active_conn) {
|
||||
if (ble_hid_is_connected()) {
|
||||
publish_hid_protocol_event(HID_PROTO_REPORT);
|
||||
}
|
||||
break;
|
||||
@@ -166,21 +190,19 @@ 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)) {
|
||||
__ASSERT_NO_MSG(ble_hid.link.conn == NULL);
|
||||
ble_hid.link.conn = event->id;
|
||||
if (bt_hids_connected(&hids_obj, ble_hid.link.conn)) {
|
||||
LOG_WRN("bt_hids_connected failed");
|
||||
}
|
||||
/* 连接建立后按当前协议主动同步一次,避免 keyboard_module 等待下一次 set_protocol。 */
|
||||
publish_hid_protocol_event(pm_to_protocol(current_pm));
|
||||
break;
|
||||
|
||||
case PEER_STATE_DISCONNECTED:
|
||||
if (active_conn == event->id) {
|
||||
if (bt_hids_disconnected(&hids_obj, active_conn)) {
|
||||
if (ble_hid.link.conn == event->id) {
|
||||
if (bt_hids_disconnected(&hids_obj, ble_hid.link.conn)) {
|
||||
LOG_WRN("bt_hids_disconnected failed");
|
||||
}
|
||||
active_conn = NULL;
|
||||
ble_hid.link.conn = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -189,73 +211,76 @@ static void handle_ble_peer_event(const struct ble_peer_event *event)
|
||||
}
|
||||
}
|
||||
|
||||
static bool handle_hid_report_event(const struct hid_report_event *event)
|
||||
static bool handle_hid_boot_event(const struct hid_boot_event *event)
|
||||
{
|
||||
if (!ble_mode_selected || !active_conn) {
|
||||
if (!ble_hid_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ble_hid_is_boot_mode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *payload = hid_boot_event_get_data(event);
|
||||
size_t payload_len = hid_boot_event_get_size(event);
|
||||
|
||||
if (payload_len != BOOT_KEYBOARD_REPORT_LEN) {
|
||||
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = bt_hids_boot_kb_inp_rep_send(&hids_obj, ble_hid.link.conn,
|
||||
payload, payload_len, NULL);
|
||||
|
||||
if (err) {
|
||||
LOG_WRN("BLE HID boot send failed err=%d", err);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_hid_report_event(const struct hid_report_event *event)
|
||||
{
|
||||
if (!ble_hid_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ble_hid_is_report_mode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *data = hid_report_event_get_data(event);
|
||||
size_t data_len = hid_report_event_get_size(event);
|
||||
uint8_t report_id;
|
||||
const uint8_t *payload;
|
||||
size_t payload_len;
|
||||
|
||||
/*
|
||||
* keyboard_module 已经按照 protocol 打包好了完整 payload。
|
||||
* BLE 模块这里只做协议一致性检查与发送。
|
||||
*/
|
||||
if ((current_pm == BT_HIDS_PM_BOOT) && (event->protocol != HID_PROTO_BOOT)) {
|
||||
return false;
|
||||
}
|
||||
if ((current_pm == BT_HIDS_PM_REPORT) && (event->protocol != HID_PROTO_REPORT)) {
|
||||
if (data_len < 1U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->protocol == HID_PROTO_BOOT) {
|
||||
report_id = REPORT_ID_KEYBOARD;
|
||||
payload = event->dyndata.data;
|
||||
payload_len = event->dyndata.size;
|
||||
report_id = data[0];
|
||||
payload = &data[1];
|
||||
payload_len = data_len - 1U;
|
||||
|
||||
uint8_t rep_index;
|
||||
|
||||
if (report_id == REPORT_ID_KEYBOARD) {
|
||||
rep_index = 0U;
|
||||
} else if (report_id == REPORT_ID_CONSUMER) {
|
||||
rep_index = 1U;
|
||||
} else {
|
||||
if (event->dyndata.size < 1U) {
|
||||
return false;
|
||||
}
|
||||
report_id = event->dyndata.data[0];
|
||||
payload = &event->dyndata.data[1];
|
||||
payload_len = event->dyndata.size - 1U;
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = 0;
|
||||
|
||||
if (event->protocol == HID_PROTO_BOOT) {
|
||||
if (report_id != REPORT_ID_KEYBOARD) {
|
||||
return false;
|
||||
}
|
||||
if (payload_len != BOOT_KEYBOARD_REPORT_LEN) {
|
||||
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
err = bt_hids_boot_kb_inp_rep_send(&hids_obj, active_conn,
|
||||
payload, payload_len, NULL);
|
||||
} else {
|
||||
uint8_t rep_index;
|
||||
|
||||
if (report_id == REPORT_ID_KEYBOARD) {
|
||||
rep_index = 0U;
|
||||
} else if (report_id == REPORT_ID_CONSUMER) {
|
||||
rep_index = 1U;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload_len > UINT8_MAX) {
|
||||
LOG_WRN("Payload too large=%u", payload_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
err = bt_hids_inp_rep_send(&hids_obj, active_conn, rep_index,
|
||||
payload, (uint8_t)payload_len, NULL);
|
||||
if (payload_len > UINT8_MAX) {
|
||||
LOG_WRN("Payload too large=%u", payload_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = bt_hids_inp_rep_send(&hids_obj, ble_hid.link.conn, rep_index,
|
||||
payload, (uint8_t)payload_len, NULL);
|
||||
|
||||
if (err) {
|
||||
LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err);
|
||||
}
|
||||
@@ -292,7 +317,7 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
|
||||
if (is_mode_event(aeh)) {
|
||||
const struct mode_event *event = cast_mode_event(aeh);
|
||||
ble_mode_selected = (event->mode_type == MODE_TYPE_BLE);
|
||||
ble_hid.policy.ble_mode_selected = (event->mode_type == MODE_TYPE_BLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -300,6 +325,10 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
return handle_hid_report_event(cast_hid_report_event(aeh));
|
||||
}
|
||||
|
||||
if (is_hid_boot_event(aeh)) {
|
||||
return handle_hid_boot_event(cast_hid_boot_event(aeh));
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(false);
|
||||
return false;
|
||||
}
|
||||
@@ -308,4 +337,5 @@ APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_boot_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_report_event);
|
||||
|
||||
281
src/modules/ble_slot_ctrl_module.c
Normal file
281
src/modules/ble_slot_ctrl_module.c
Normal file
@@ -0,0 +1,281 @@
|
||||
#include <string.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <app_event_manager.h>
|
||||
|
||||
#define MODULE ble_slot_ctrl
|
||||
#include <caf/events/button_event.h>
|
||||
#include <caf/events/module_state_event.h>
|
||||
#include <caf/events/power_event.h>
|
||||
#include <caf/key_id.h>
|
||||
|
||||
#include "config_event.h"
|
||||
#include "mode_event.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define BLE_BOND_CFG_MODULE_ID 0x01
|
||||
#define BLE_BOND_CFG_PEER_SELECT 0
|
||||
#define BLE_BOND_CFG_PEER_ERASE 1
|
||||
|
||||
#define BLE_SLOT_CTRL_MUTE_HOLD_MS 3000
|
||||
#define BLE_SLOT_CTRL_SLOT_KEY_NONE UINT16_MAX
|
||||
#define BLE_SLOT_CTRL_KEY_MUTE KEY_ID(3, 0)
|
||||
#define BLE_SLOT_CTRL_SLOT_COUNT 3
|
||||
|
||||
struct ble_slot_ctrl_ctx {
|
||||
bool ble_mode_selected;
|
||||
bool mute_pressed;
|
||||
bool mute_hold_fired;
|
||||
bool slot_combo_used;
|
||||
bool initialized;
|
||||
bool passthrough_mute_press;
|
||||
bool passthrough_mute_release;
|
||||
uint16_t slot_key_id;
|
||||
struct k_work_delayable mute_hold_work;
|
||||
};
|
||||
|
||||
static struct ble_slot_ctrl_ctx slot_ctrl = {
|
||||
.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE,
|
||||
};
|
||||
|
||||
static uint8_t ble_slot_ctrl_make_event_id(uint8_t opt_id)
|
||||
{
|
||||
return (BLE_BOND_CFG_MODULE_ID << MOD_FIELD_POS) | (uint8_t)(opt_id + 1U);
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_submit_config_request(uint8_t opt_id,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len)
|
||||
{
|
||||
struct config_event *event = new_config_event(payload_len);
|
||||
|
||||
event->transport_id = 0U;
|
||||
event->is_request = true;
|
||||
event->event_id = ble_slot_ctrl_make_event_id(opt_id);
|
||||
event->recipient = CFG_CHAN_RECIPIENT_LOCAL;
|
||||
event->status = CONFIG_STATUS_SET;
|
||||
|
||||
if ((payload_len > 0U) && (payload != NULL)) {
|
||||
memcpy(event->dyndata.data, payload, payload_len);
|
||||
}
|
||||
|
||||
LOG_INF("Submit cfg request opt=%u len=%u", opt_id, payload_len);
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_request_select(uint8_t slot_id)
|
||||
{
|
||||
ble_slot_ctrl_submit_config_request(BLE_BOND_CFG_PEER_SELECT, &slot_id, sizeof(slot_id));
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_request_erase_current(void)
|
||||
{
|
||||
ble_slot_ctrl_submit_config_request(BLE_BOND_CFG_PEER_ERASE, NULL, 0U);
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_reset(void)
|
||||
{
|
||||
LOG_DBG("Reset slot ctrl state mute=%d hold=%d combo=%d key=0x%04x",
|
||||
slot_ctrl.mute_pressed,
|
||||
slot_ctrl.mute_hold_fired,
|
||||
slot_ctrl.slot_combo_used,
|
||||
slot_ctrl.slot_key_id);
|
||||
|
||||
slot_ctrl.mute_pressed = false;
|
||||
slot_ctrl.mute_hold_fired = false;
|
||||
slot_ctrl.slot_combo_used = false;
|
||||
slot_ctrl.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE;
|
||||
|
||||
if (slot_ctrl.initialized) {
|
||||
(void)k_work_cancel_delayable(&slot_ctrl.mute_hold_work);
|
||||
}
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_submit_button(uint16_t key_id, bool pressed)
|
||||
{
|
||||
struct button_event *event = new_button_event();
|
||||
|
||||
event->key_id = key_id;
|
||||
event->pressed = pressed;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_forward_mute_tap(void)
|
||||
{
|
||||
LOG_INF("Forward mute tap to HID path");
|
||||
slot_ctrl.passthrough_mute_press = true;
|
||||
slot_ctrl.passthrough_mute_release = true;
|
||||
ble_slot_ctrl_submit_button(BLE_SLOT_CTRL_KEY_MUTE, true);
|
||||
ble_slot_ctrl_submit_button(BLE_SLOT_CTRL_KEY_MUTE, false);
|
||||
}
|
||||
|
||||
static bool ble_slot_ctrl_try_get_slot_id(uint16_t key_id, uint8_t *slot_id)
|
||||
{
|
||||
switch (key_id) {
|
||||
case KEY_ID(0, 4):
|
||||
*slot_id = 0U;
|
||||
return true;
|
||||
case KEY_ID(1, 4):
|
||||
*slot_id = 1U;
|
||||
return true;
|
||||
case KEY_ID(2, 4):
|
||||
*slot_id = 2U;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ble_slot_ctrl_active(void)
|
||||
{
|
||||
return slot_ctrl.ble_mode_selected;
|
||||
}
|
||||
|
||||
static void ble_slot_ctrl_mute_hold_work_fn(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
|
||||
if (!ble_slot_ctrl_active() ||
|
||||
!slot_ctrl.mute_pressed ||
|
||||
slot_ctrl.slot_combo_used) {
|
||||
LOG_DBG("Ignore mute hold active=%d mute=%d combo=%d",
|
||||
ble_slot_ctrl_active(),
|
||||
slot_ctrl.mute_pressed,
|
||||
slot_ctrl.slot_combo_used);
|
||||
return;
|
||||
}
|
||||
|
||||
ble_slot_ctrl_request_erase_current();
|
||||
slot_ctrl.mute_hold_fired = true;
|
||||
LOG_INF("Requested erase current BLE slot by mute hold");
|
||||
}
|
||||
|
||||
static bool handle_button_event(const struct button_event *event)
|
||||
{
|
||||
if (!ble_slot_ctrl_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->key_id == BLE_SLOT_CTRL_KEY_MUTE) {
|
||||
if (event->pressed && slot_ctrl.passthrough_mute_press) {
|
||||
LOG_DBG("Pass through synthetic mute press");
|
||||
slot_ctrl.passthrough_mute_press = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!event->pressed && slot_ctrl.passthrough_mute_release) {
|
||||
LOG_DBG("Pass through synthetic mute release");
|
||||
slot_ctrl.passthrough_mute_release = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->pressed) {
|
||||
if (slot_ctrl.mute_pressed) {
|
||||
LOG_DBG("Drop repeated mute press");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INF("Mute pressed: start hold timer %u ms", BLE_SLOT_CTRL_MUTE_HOLD_MS);
|
||||
slot_ctrl.mute_pressed = true;
|
||||
slot_ctrl.mute_hold_fired = false;
|
||||
slot_ctrl.slot_combo_used = false;
|
||||
slot_ctrl.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE;
|
||||
k_work_reschedule(&slot_ctrl.mute_hold_work,
|
||||
K_MSEC(BLE_SLOT_CTRL_MUTE_HOLD_MS));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!slot_ctrl.mute_pressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INF("Mute released hold=%d combo=%d",
|
||||
slot_ctrl.mute_hold_fired,
|
||||
slot_ctrl.slot_combo_used);
|
||||
(void)k_work_cancel_delayable(&slot_ctrl.mute_hold_work);
|
||||
|
||||
if (!slot_ctrl.mute_hold_fired && !slot_ctrl.slot_combo_used) {
|
||||
ble_slot_ctrl_forward_mute_tap();
|
||||
}
|
||||
|
||||
ble_slot_ctrl_reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!slot_ctrl.mute_pressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!event->pressed && (slot_ctrl.slot_key_id == event->key_id)) {
|
||||
slot_ctrl.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!event->pressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t slot_id;
|
||||
|
||||
if (!ble_slot_ctrl_try_get_slot_id(event->key_id, &slot_id)) {
|
||||
LOG_DBG("Mute combo ignore key_id=0x%04x", event->key_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)k_work_cancel_delayable(&slot_ctrl.mute_hold_work);
|
||||
slot_ctrl.slot_combo_used = true;
|
||||
slot_ctrl.slot_key_id = event->key_id;
|
||||
ble_slot_ctrl_request_select(slot_id);
|
||||
LOG_INF("Requested BLE slot=%u from key_id=0x%04x", slot_id, event->key_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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)) {
|
||||
if (!slot_ctrl.initialized) {
|
||||
k_work_init_delayable(&slot_ctrl.mute_hold_work,
|
||||
ble_slot_ctrl_mute_hold_work_fn);
|
||||
slot_ctrl.initialized = true;
|
||||
}
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_mode_event(aeh)) {
|
||||
const struct mode_event *event = cast_mode_event(aeh);
|
||||
|
||||
slot_ctrl.ble_mode_selected = mode_event_is_ble(event);
|
||||
LOG_INF("BLE slot ctrl mode selected=%d", slot_ctrl.ble_mode_selected);
|
||||
if (!slot_ctrl.ble_mode_selected) {
|
||||
ble_slot_ctrl_reset();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
ble_slot_ctrl_reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_button_event(aeh)) {
|
||||
return handle_button_event(cast_button_event(aeh));
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, button_event);
|
||||
@@ -10,9 +10,9 @@
|
||||
#include <caf/key_id.h>
|
||||
|
||||
#include "hid_report_descriptor.h"
|
||||
#include "hid_boot_event.h"
|
||||
#include "hid_protocol_event.h"
|
||||
#include "hid_report_event.h"
|
||||
#include "mode_event.h"
|
||||
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
@@ -103,23 +103,19 @@ static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id)
|
||||
struct keyboard_state {
|
||||
uint8_t modifier_bm;
|
||||
uint8_t usage_bm[KEYBOARD_BITMAP_SIZE];
|
||||
enum hid_protocol_type ble_protocol;
|
||||
enum hid_protocol_type usb_protocol;
|
||||
mode_type_t current_mode;
|
||||
enum hid_protocol_type current_protocol;
|
||||
uint16_t consumer_usage;
|
||||
};
|
||||
|
||||
static struct keyboard_state ks = {
|
||||
.ble_protocol = HID_PROTO_REPORT,
|
||||
.usb_protocol = HID_PROTO_REPORT,
|
||||
.current_mode = MODE_TYPE_COUNT,
|
||||
.current_protocol = HID_PROTO_REPORT,
|
||||
.consumer_usage = 0,
|
||||
};
|
||||
|
||||
/* 依据当前 mode 选择生效的 HID 协议来源(BLE 或 USB)。 */
|
||||
/* 当前 HID 报告编码仅跟最近一次 set_protocol 结果相关。 */
|
||||
static enum hid_protocol_type active_protocol_get(void)
|
||||
{
|
||||
return (ks.current_mode == MODE_TYPE_USB) ? ks.usb_protocol : ks.ble_protocol;
|
||||
return ks.current_protocol;
|
||||
}
|
||||
|
||||
/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */
|
||||
@@ -182,18 +178,17 @@ static void submit_hid_report(enum hid_protocol_type protocol,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len)
|
||||
{
|
||||
size_t report_len = (protocol == HID_PROTO_REPORT) ? (payload_len + 1U) : payload_len;
|
||||
struct hid_report_event *event = new_hid_report_event(report_len);
|
||||
uint8_t report_buf[KEYBOARD_REPORT_PAYLOAD + 1U];
|
||||
|
||||
event->protocol = protocol;
|
||||
if (protocol == HID_PROTO_REPORT) {
|
||||
event->dyndata.data[0] = report_id;
|
||||
memcpy(&event->dyndata.data[1], payload, payload_len);
|
||||
} else {
|
||||
memcpy(event->dyndata.data, payload, payload_len);
|
||||
}
|
||||
size_t report_len = payload_len + 1U;
|
||||
|
||||
APP_EVENT_SUBMIT(event);
|
||||
report_buf[0] = report_id;
|
||||
memcpy(&report_buf[1], payload, payload_len);
|
||||
hid_report_event_submit(report_buf, report_len);
|
||||
} else {
|
||||
hid_boot_event_submit(payload, payload_len);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -309,24 +304,10 @@ static bool handle_button_event(const struct button_event *event)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 同步 BLE/USB 传输层上报的当前协议。 */
|
||||
/* 记录最近一次 set_protocol 结果。 */
|
||||
static bool handle_hid_protocol_event(const struct hid_protocol_event *event)
|
||||
{
|
||||
if (event->transport == HID_TRANSPORT_BLE) {
|
||||
ks.ble_protocol = event->protocol;
|
||||
} else if (event->transport == HID_TRANSPORT_USB) {
|
||||
ks.usb_protocol = event->protocol;
|
||||
} else {
|
||||
__ASSERT_NO_MSG(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 更新当前激活模式,决定协议来源取 BLE 还是 USB。 */
|
||||
static bool handle_mode_event(const struct mode_event *event)
|
||||
{
|
||||
ks.current_mode = event->mode_type;
|
||||
ks.current_protocol = hid_protocol_event_get_protocol(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -341,10 +322,6 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
return handle_hid_protocol_event(cast_hid_protocol_event(aeh));
|
||||
}
|
||||
|
||||
if (is_mode_event(aeh)) {
|
||||
return handle_mode_event(cast_mode_event(aeh));
|
||||
}
|
||||
|
||||
if (is_module_state_event(aeh)) {
|
||||
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||
|
||||
@@ -364,5 +341,4 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, button_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <caf/events/ble_common_event.h>
|
||||
#include <caf/events/led_event.h>
|
||||
|
||||
#include "keyboard_led_state_event.h"
|
||||
#include "keyboard_led_event.h"
|
||||
#include "led_state.h"
|
||||
#include "mode_event.h"
|
||||
#include "led_state_def.h"
|
||||
@@ -129,9 +129,9 @@ static bool handle_ble_peer_operation_event(const struct ble_peer_operation_even
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_keyboard_led_state_event(const struct keyboard_led_state_event *event)
|
||||
static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
|
||||
{
|
||||
num_lock_on = event->num_lock;
|
||||
num_lock_on = keyboard_led_event_is_num_lock_on(event);
|
||||
submit_num_lock_led();
|
||||
return false;
|
||||
}
|
||||
@@ -153,8 +153,8 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
if (is_mode_event(aeh))
|
||||
return handle_mode_event(cast_mode_event(aeh));
|
||||
|
||||
if (is_keyboard_led_state_event(aeh))
|
||||
return handle_keyboard_led_state_event(cast_keyboard_led_state_event(aeh));
|
||||
if (is_keyboard_led_event(aeh))
|
||||
return handle_keyboard_led_event(cast_keyboard_led_event(aeh));
|
||||
|
||||
if (IS_ENABLED(CONFIG_CAF_BLE_COMMON_EVENTS) && is_ble_peer_event(aeh))
|
||||
return handle_ble_peer_event(cast_ble_peer_event(aeh));
|
||||
@@ -172,7 +172,7 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event);
|
||||
#ifdef CONFIG_CAF_BLE_COMMON_EVENTS
|
||||
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_search_event);
|
||||
|
||||
@@ -115,10 +115,7 @@ static void publish_mode_event(mode_type_t mode)
|
||||
return;
|
||||
|
||||
current_mode = mode;
|
||||
struct mode_event *event = new_mode_event();
|
||||
|
||||
event->mode_type = mode;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
mode_event_submit(mode);
|
||||
/*
|
||||
* 模式切换是明确的人机交互动作。这里同步上报 keep_alive_event,
|
||||
* 让 power manager 重置休眠倒计时,避免用户刚切换模式就进入省电流程。
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
#include <caf/events/module_state_event.h>
|
||||
|
||||
#include "hid_report_descriptor.h"
|
||||
#include "hid_boot_event.h"
|
||||
#include "hid_protocol_event.h"
|
||||
#include "hid_report_event.h"
|
||||
#include "keyboard_led_state_event.h"
|
||||
#include "keyboard_led_event.h"
|
||||
#include "mode_event.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
@@ -35,24 +36,30 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
* - 只有当 mode 切到 USB 且系统非休眠时才 enable;
|
||||
* - BLE 逻辑保持不变,不在本模块中触碰。
|
||||
*/
|
||||
struct usb_hid_ctx {
|
||||
const struct device *boot_dev;
|
||||
const struct device *nkro_dev;
|
||||
const struct device *raw_dev;
|
||||
struct usb_hid_iface {
|
||||
const struct device *dev;
|
||||
bool iface_ready;
|
||||
bool in_flight;
|
||||
};
|
||||
|
||||
bool stack_initialized;
|
||||
bool stack_enabled;
|
||||
bool stack_error;
|
||||
enum usb_hid_stack_state {
|
||||
USB_HID_STACK_STATE_OFF,
|
||||
USB_HID_STACK_STATE_READY,
|
||||
USB_HID_STACK_STATE_ACTIVE,
|
||||
USB_HID_STACK_STATE_ERROR,
|
||||
};
|
||||
|
||||
struct usb_hid_policy {
|
||||
bool usb_mode_selected;
|
||||
bool pm_suspended;
|
||||
};
|
||||
|
||||
bool boot_iface_ready;
|
||||
bool nkro_iface_ready;
|
||||
bool raw_iface_ready;
|
||||
bool boot_in_flight;
|
||||
bool nkro_in_flight;
|
||||
|
||||
struct usb_hid_ctx {
|
||||
struct usb_hid_iface boot;
|
||||
struct usb_hid_iface nkro;
|
||||
struct usb_hid_iface raw;
|
||||
enum usb_hid_stack_state stack_state;
|
||||
struct usb_hid_policy policy;
|
||||
enum hid_protocol_type current_protocol;
|
||||
};
|
||||
|
||||
@@ -73,22 +80,54 @@ static const uint8_t boot_report_desc[] = HID_KEYBOARD_REPORT_DESC();
|
||||
static const uint8_t nkro_report_desc[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
|
||||
static const uint8_t raw_report_desc[] = HID_DESC_RAW_64();
|
||||
|
||||
/* 统一入口仅处理单字节 LED 报告并发布事件。 */
|
||||
static void process_usb_led_input_report(uint8_t led_report)
|
||||
static bool usb_hid_stack_is_active(void)
|
||||
{
|
||||
struct keyboard_led_state_event *event = new_keyboard_led_state_event();
|
||||
return g_usb_hid.stack_state == USB_HID_STACK_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
event->led_mask = led_report;
|
||||
event->num_lock = (led_report & BIT(0)) != 0U;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
static bool usb_hid_stack_is_error(void)
|
||||
{
|
||||
return g_usb_hid.stack_state == USB_HID_STACK_STATE_ERROR;
|
||||
}
|
||||
|
||||
static bool usb_hid_should_be_active(void)
|
||||
{
|
||||
return g_usb_hid.policy.usb_mode_selected && !g_usb_hid.policy.pm_suspended;
|
||||
}
|
||||
|
||||
static struct usb_hid_iface *usb_hid_iface_from_dev(const struct device *dev)
|
||||
{
|
||||
if (dev == g_usb_hid.boot.dev) {
|
||||
return &g_usb_hid.boot;
|
||||
}
|
||||
|
||||
if (dev == g_usb_hid.nkro.dev) {
|
||||
return &g_usb_hid.nkro;
|
||||
}
|
||||
|
||||
if (dev == g_usb_hid.raw.dev) {
|
||||
return &g_usb_hid.raw;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void usb_hid_clear_runtime_iface_state(void)
|
||||
{
|
||||
g_usb_hid.boot.iface_ready = false;
|
||||
g_usb_hid.nkro.iface_ready = false;
|
||||
g_usb_hid.raw.iface_ready = false;
|
||||
g_usb_hid.boot.in_flight = false;
|
||||
g_usb_hid.nkro.in_flight = false;
|
||||
g_usb_hid.raw.in_flight = false;
|
||||
}
|
||||
|
||||
static bool should_handle_led_input_from_dev(const struct device *dev)
|
||||
{
|
||||
if (g_usb_hid.current_protocol == HID_PROTO_BOOT)
|
||||
return (dev == g_usb_hid.boot_dev);
|
||||
return (dev == g_usb_hid.boot.dev);
|
||||
|
||||
return (dev == g_usb_hid.nkro_dev);
|
||||
return (dev == g_usb_hid.nkro.dev);
|
||||
}
|
||||
|
||||
static bool try_extract_led_mask(const struct device *dev,
|
||||
@@ -99,12 +138,12 @@ static bool try_extract_led_mask(const struct device *dev,
|
||||
if ((buf == NULL) || (len == 0U))
|
||||
return false;
|
||||
|
||||
if (dev == g_usb_hid.boot_dev) {
|
||||
if (dev == g_usb_hid.boot.dev) {
|
||||
*led_mask = buf[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dev != g_usb_hid.nkro_dev)
|
||||
if (dev != g_usb_hid.nkro.dev)
|
||||
return false;
|
||||
|
||||
if (len >= 2U) {
|
||||
@@ -149,7 +188,7 @@ static int hid_stub_set_report(const struct device *dev,
|
||||
}
|
||||
|
||||
LOG_INF("hid_stub_set_report led_mask=0x%02x", led_mask);
|
||||
process_usb_led_input_report(led_mask);
|
||||
keyboard_led_event_submit(led_mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -185,12 +224,8 @@ static void hid_stub_set_protocol(const struct device *dev, uint8_t proto)
|
||||
* 按需求:USB HID 在连接后收到 set_protocol 时上报 hid_protocol_event。
|
||||
* 这里额外检查接口 ready,避免在未枚举完成阶段上报无意义协议切换。
|
||||
*/
|
||||
if (g_usb_hid.boot_iface_ready || g_usb_hid.nkro_iface_ready) {
|
||||
struct hid_protocol_event *event = new_hid_protocol_event();
|
||||
|
||||
event->transport = HID_TRANSPORT_USB;
|
||||
event->protocol = new_protocol;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
if (g_usb_hid.boot.iface_ready || g_usb_hid.nkro.iface_ready) {
|
||||
hid_protocol_event_submit(new_protocol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,13 +238,10 @@ static void hid_stub_input_done(const struct device *dev, const uint8_t *report)
|
||||
* - 仅在这里清除“在途发送”标志,确保“上一包未完成则丢弃新包”的策略可闭环;
|
||||
* - 若收到未知 dev 的回调,仅记录告警,避免静默状态错乱。
|
||||
*/
|
||||
if (dev == g_usb_hid.boot_dev) {
|
||||
g_usb_hid.boot_in_flight = false;
|
||||
return;
|
||||
}
|
||||
struct usb_hid_iface *iface = usb_hid_iface_from_dev(dev);
|
||||
|
||||
if (dev == g_usb_hid.nkro_dev) {
|
||||
g_usb_hid.nkro_in_flight = false;
|
||||
if (iface) {
|
||||
iface->in_flight = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -229,34 +261,21 @@ static void hid_stub_output_report(const struct device *dev, uint16_t len, const
|
||||
}
|
||||
|
||||
LOG_INF("hid_stub_output_report led_mask=0x%02x", led_mask);
|
||||
process_usb_led_input_report(led_mask);
|
||||
keyboard_led_event_submit(led_mask);
|
||||
}
|
||||
|
||||
static void hid_iface_ready_cb(const struct device *dev, bool ready)
|
||||
{
|
||||
if (dev == g_usb_hid.boot_dev) {
|
||||
g_usb_hid.boot_iface_ready = ready;
|
||||
if (!ready) {
|
||||
g_usb_hid.boot_in_flight = false;
|
||||
}
|
||||
} else if (dev == g_usb_hid.nkro_dev) {
|
||||
g_usb_hid.nkro_iface_ready = ready;
|
||||
if (!ready) {
|
||||
g_usb_hid.nkro_in_flight = false;
|
||||
}
|
||||
} else if (dev == g_usb_hid.raw_dev) {
|
||||
g_usb_hid.raw_iface_ready = ready;
|
||||
struct usb_hid_iface *iface = usb_hid_iface_from_dev(dev);
|
||||
|
||||
if (!iface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ready) {
|
||||
/* 连接可用后同步一次当前协议,让 keyboard_module 与传输侧编码一致。 */
|
||||
struct hid_protocol_event *event = new_hid_protocol_event();
|
||||
|
||||
event->transport = HID_TRANSPORT_USB;
|
||||
event->protocol = g_usb_hid.current_protocol;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
iface->iface_ready = ready;
|
||||
if (!ready) {
|
||||
iface->in_flight = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static const struct hid_device_ops boot_hid_ops = {
|
||||
@@ -296,7 +315,7 @@ static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
|
||||
{
|
||||
switch (msg->type) {
|
||||
case USBD_MSG_VBUS_READY:
|
||||
if (g_usb_hid.pm_suspended) {
|
||||
if (g_usb_hid.policy.pm_suspended) {
|
||||
LOG_INF("VBUS ready: submit wake_up_event");
|
||||
APP_EVENT_SUBMIT(new_wake_up_event());
|
||||
}
|
||||
@@ -305,7 +324,7 @@ static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
|
||||
* 只有在 USB 模式下才允许拉起 USB 栈。
|
||||
* 这样即使插着线,只要用户切到 BLE/2.4G,也不会强制进入 USB HID。
|
||||
*/
|
||||
if (usbd_can_detect_vbus(usbd_ctx) && g_usb_hid.stack_enabled) {
|
||||
if (usbd_can_detect_vbus(usbd_ctx) && usb_hid_stack_is_active()) {
|
||||
(void)usbd_enable(usbd_ctx);
|
||||
}
|
||||
break;
|
||||
@@ -321,7 +340,7 @@ static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
|
||||
case USBD_MSG_UDC_ERROR:
|
||||
case USBD_MSG_STACK_ERROR:
|
||||
LOG_ERR("USBD stack error message: %d", msg->type);
|
||||
g_usb_hid.stack_error = true;
|
||||
g_usb_hid.stack_state = USB_HID_STACK_STATE_ERROR;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -331,17 +350,17 @@ static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
|
||||
|
||||
static bool usb_hid_devices_ready(void)
|
||||
{
|
||||
if (!device_is_ready(g_usb_hid.boot_dev)) {
|
||||
if (!device_is_ready(g_usb_hid.boot.dev)) {
|
||||
LOG_ERR("HID boot device is not ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!device_is_ready(g_usb_hid.nkro_dev)) {
|
||||
if (!device_is_ready(g_usb_hid.nkro.dev)) {
|
||||
LOG_ERR("HID nkro device is not ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!device_is_ready(g_usb_hid.raw_dev)) {
|
||||
if (!device_is_ready(g_usb_hid.raw.dev)) {
|
||||
LOG_ERR("HID raw device is not ready");
|
||||
return false;
|
||||
}
|
||||
@@ -356,7 +375,7 @@ static bool usb_hid_devices_ready(void)
|
||||
|
||||
static int usb_hid_register_hid_devices(void)
|
||||
{
|
||||
int err = hid_device_register(g_usb_hid.boot_dev,
|
||||
int err = hid_device_register(g_usb_hid.boot.dev,
|
||||
boot_report_desc, sizeof(boot_report_desc),
|
||||
&boot_hid_ops);
|
||||
if (err) {
|
||||
@@ -364,7 +383,7 @@ static int usb_hid_register_hid_devices(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = hid_device_register(g_usb_hid.nkro_dev,
|
||||
err = hid_device_register(g_usb_hid.nkro.dev,
|
||||
nkro_report_desc, sizeof(nkro_report_desc),
|
||||
&report_hid_ops);
|
||||
if (err) {
|
||||
@@ -372,7 +391,7 @@ static int usb_hid_register_hid_devices(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = hid_device_register(g_usb_hid.raw_dev,
|
||||
err = hid_device_register(g_usb_hid.raw.dev,
|
||||
raw_report_desc, sizeof(raw_report_desc),
|
||||
&raw_hid_ops);
|
||||
if (err) {
|
||||
@@ -441,13 +460,13 @@ static int usb_hid_init_usbd_stack(void)
|
||||
|
||||
static int usb_hid_stack_init(void)
|
||||
{
|
||||
if (g_usb_hid.stack_initialized) {
|
||||
if (g_usb_hid.stack_state != USB_HID_STACK_STATE_OFF) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_usb_hid.boot_dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_0));
|
||||
g_usb_hid.nkro_dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_1));
|
||||
g_usb_hid.raw_dev = DEVICE_DT_GET(DT_NODELABEL(raw_hid));
|
||||
g_usb_hid.boot.dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_0));
|
||||
g_usb_hid.nkro.dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_1));
|
||||
g_usb_hid.raw.dev = DEVICE_DT_GET(DT_NODELABEL(raw_hid));
|
||||
|
||||
if (!usb_hid_devices_ready()) {
|
||||
return -ENODEV;
|
||||
@@ -468,7 +487,7 @@ static int usb_hid_stack_init(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
g_usb_hid.stack_initialized = true;
|
||||
g_usb_hid.stack_state = USB_HID_STACK_STATE_READY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -476,36 +495,39 @@ static int usb_hid_set_enabled(bool enable)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!g_usb_hid.stack_initialized) {
|
||||
if (usb_hid_stack_is_error()) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (g_usb_hid.stack_state == USB_HID_STACK_STATE_OFF) {
|
||||
err = usb_hid_stack_init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_usb_hid.stack_enabled == enable) {
|
||||
if (enable && usb_hid_stack_is_active()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_usb_hid.stack_enabled = enable;
|
||||
if (!enable && (g_usb_hid.stack_state == USB_HID_STACK_STATE_READY)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
err = usbd_enable(&new_kbd_usbd);
|
||||
} else {
|
||||
err = usbd_disable(&new_kbd_usbd);
|
||||
g_usb_hid.boot_iface_ready = false;
|
||||
g_usb_hid.nkro_iface_ready = false;
|
||||
g_usb_hid.raw_iface_ready = false;
|
||||
g_usb_hid.boot_in_flight = false;
|
||||
g_usb_hid.nkro_in_flight = false;
|
||||
usb_hid_clear_runtime_iface_state();
|
||||
}
|
||||
|
||||
if (err && (err != -EALREADY)) {
|
||||
LOG_ERR("usbd_%s failed: %d", enable ? "enable" : "disable", err);
|
||||
g_usb_hid.stack_error = true;
|
||||
g_usb_hid.stack_state = USB_HID_STACK_STATE_ERROR;
|
||||
return err;
|
||||
}
|
||||
|
||||
g_usb_hid.stack_state = enable ? USB_HID_STACK_STATE_ACTIVE : USB_HID_STACK_STATE_READY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -516,7 +538,7 @@ static void refresh_usb_state_by_policy(void)
|
||||
* - USB 模式 + 非休眠:启用 USB HID。
|
||||
* - 其他情况:关闭 USB HID(不销毁初始化结果,后续可快速恢复)。
|
||||
*/
|
||||
bool should_enable = g_usb_hid.usb_mode_selected && !g_usb_hid.pm_suspended;
|
||||
bool should_enable = usb_hid_should_be_active();
|
||||
int err = usb_hid_set_enabled(should_enable);
|
||||
|
||||
if (err) {
|
||||
@@ -533,7 +555,7 @@ static bool handle_module_state_event(const struct module_state_event *event)
|
||||
int err = usb_hid_stack_init();
|
||||
if (err) {
|
||||
LOG_ERR("USB HID stack init failed: %d", err);
|
||||
g_usb_hid.stack_error = true;
|
||||
g_usb_hid.stack_state = USB_HID_STACK_STATE_ERROR;
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
@@ -544,19 +566,19 @@ static bool handle_module_state_event(const struct module_state_event *event)
|
||||
|
||||
static bool handle_mode_event(const struct mode_event *event)
|
||||
{
|
||||
g_usb_hid.usb_mode_selected = (event->mode_type == MODE_TYPE_USB);
|
||||
g_usb_hid.policy.usb_mode_selected = (event->mode_type == MODE_TYPE_USB);
|
||||
refresh_usb_state_by_policy();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_power_down_event(void)
|
||||
{
|
||||
if (g_usb_hid.pm_suspended) {
|
||||
if (g_usb_hid.policy.pm_suspended) {
|
||||
/* 避免重复上报 STANDBY 导致 power_manager 在 SUSPENDING 期间反复迭代。 */
|
||||
return false;
|
||||
}
|
||||
|
||||
g_usb_hid.pm_suspended = true;
|
||||
g_usb_hid.policy.pm_suspended = true;
|
||||
refresh_usb_state_by_policy();
|
||||
module_set_state(MODULE_STATE_STANDBY);
|
||||
return false;
|
||||
@@ -564,16 +586,49 @@ static bool handle_power_down_event(void)
|
||||
|
||||
static bool handle_wake_up_event(void)
|
||||
{
|
||||
if (!g_usb_hid.pm_suspended) {
|
||||
if (!g_usb_hid.policy.pm_suspended) {
|
||||
return false;
|
||||
}
|
||||
|
||||
g_usb_hid.pm_suspended = false;
|
||||
g_usb_hid.policy.pm_suspended = false;
|
||||
refresh_usb_state_by_policy();
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_hid_boot_event(const struct hid_boot_event *event)
|
||||
{
|
||||
if (!g_usb_hid.policy.usb_mode_selected || !usb_hid_stack_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_usb_hid.current_protocol != HID_PROTO_BOOT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *payload = hid_boot_event_get_data(event);
|
||||
size_t payload_len = hid_boot_event_get_size(event);
|
||||
|
||||
if (!g_usb_hid.boot.iface_ready || !g_usb_hid.boot.dev) {
|
||||
return false;
|
||||
}
|
||||
if (g_usb_hid.boot.in_flight) {
|
||||
LOG_WRN("Drop boot report: previous report not sent");
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = hid_device_submit_report(g_usb_hid.boot.dev,
|
||||
payload_len,
|
||||
payload);
|
||||
if (err) {
|
||||
LOG_WRN("USB boot report send failed err=%d", err);
|
||||
} else {
|
||||
g_usb_hid.boot.in_flight = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_hid_report_event(const struct hid_report_event *event)
|
||||
{
|
||||
/*
|
||||
@@ -581,63 +636,42 @@ static bool handle_hid_report_event(const struct hid_report_event *event)
|
||||
* - 当前 mode 为 USB;
|
||||
* - USB HID 栈已启用且对应接口 ready。
|
||||
*/
|
||||
if (!g_usb_hid.usb_mode_selected || !g_usb_hid.stack_enabled) {
|
||||
if (!g_usb_hid.policy.usb_mode_selected || !usb_hid_stack_is_active()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_usb_hid.current_protocol != HID_PROTO_REPORT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *data = hid_report_event_get_data(event);
|
||||
size_t data_len = hid_report_event_get_size(event);
|
||||
uint8_t report_id;
|
||||
|
||||
if (event->protocol != g_usb_hid.current_protocol) {
|
||||
if (data_len < 1U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->protocol == HID_PROTO_BOOT) {
|
||||
const uint8_t *payload = event->dyndata.data;
|
||||
size_t payload_len = event->dyndata.size;
|
||||
report_id = data[0];
|
||||
|
||||
if (!g_usb_hid.boot_iface_ready || !g_usb_hid.boot_dev) {
|
||||
return false;
|
||||
}
|
||||
if (g_usb_hid.boot_in_flight) {
|
||||
LOG_WRN("Drop boot report: previous report not sent");
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = hid_device_submit_report(g_usb_hid.boot_dev,
|
||||
payload_len,
|
||||
payload);
|
||||
if (err) {
|
||||
LOG_WRN("USB boot report send failed err=%d", err);
|
||||
} else {
|
||||
g_usb_hid.boot_in_flight = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->dyndata.size < 1U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report_id = event->dyndata.data[0];
|
||||
|
||||
if (!g_usb_hid.nkro_iface_ready || !g_usb_hid.nkro_dev) {
|
||||
if (!g_usb_hid.nkro.iface_ready || !g_usb_hid.nkro.dev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((report_id != REPORT_ID_KEYBOARD) && (report_id != REPORT_ID_CONSUMER)) {
|
||||
return false;
|
||||
}
|
||||
if (g_usb_hid.nkro_in_flight) {
|
||||
if (g_usb_hid.nkro.in_flight) {
|
||||
LOG_WRN("Drop report id=0x%02x: previous report not sent", report_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Report 协议下 dyndata 是 [report_id|payload],可直接透传。 */
|
||||
int err = hid_device_submit_report(g_usb_hid.nkro_dev, event->dyndata.size, event->dyndata.data);
|
||||
int err = hid_device_submit_report(g_usb_hid.nkro.dev, data_len, data);
|
||||
if (err) {
|
||||
LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err);
|
||||
} else {
|
||||
g_usb_hid.nkro_in_flight = true;
|
||||
g_usb_hid.nkro.in_flight = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -665,6 +699,10 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
return handle_hid_report_event(cast_hid_report_event(aeh));
|
||||
}
|
||||
|
||||
if (is_hid_boot_event(aeh)) {
|
||||
return handle_hid_boot_event(cast_hid_boot_event(aeh));
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(false);
|
||||
return false;
|
||||
}
|
||||
@@ -674,4 +712,5 @@ APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_boot_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);
|
||||
|
||||
Reference in New Issue
Block a user