Files
new_kbd/src/modules/ble_bond_module.c
skiinder 277462a8fe feat(ble): 添加快速广告配置并优化连接状态检查
添加了BLE快速广告相关的配置选项到prj.conf中,包括快速广告间隔、超时等参数。
同时修复了ble_bond_module中的连接状态检查逻辑,避免在挂起后保留LE连接时进行不必要的
断开操作。

在ble_hid_module和usb_hid_module中改进了HID传输事件处理逻辑,确保在相应模式未激活
或连接未建立时正确提交传输完成事件,提高了设备响应的准确性。

BREAKING CHANGE: 广告行为在连接保持情况下有所改变,可能影响配对流程。
2026-03-28 13:59:59 +08:00

584 lines
15 KiB
C

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/settings/settings.h>
#include <errno.h>
#include <app_event_manager.h>
#define MODULE ble_bond
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include <caf/events/power_event.h>
#include "config_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
/* Application-visible options carried by config_event. */
enum ble_bond_cfg_opt {
BLE_BOND_CFG_PEER_SELECT = 0,
BLE_BOND_CFG_PEER_ERASE,
BLE_BOND_CFG_PEER_ERASE_ALL,
BLE_BOND_CFG_OPT_COUNT
};
/* Module ID in config_event event_id[7:4]. Keep stable for host side tooling. */
#define BLE_BOND_CFG_MODULE_ID 0x01
#define PEER_ID_KEY "peer_id"
#define BT_LUT_KEY "bt_lut"
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
BUILD_ASSERT(BLE_BOND_SLOT_COUNT <= APP_PEER_COUNT,
"BLE slot count exceeds available Bluetooth identities");
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;
};
struct ble_bond_ctx {
enum ble_bond_state state;
struct ble_bond_storage storage;
bool auto_switch_in_progress;
};
static struct ble_bond_ctx bond = {
.state = BLE_BOND_STATE_DISABLED,
};
static const char *state_name(enum ble_bond_state s)
{
switch (s) {
case BLE_BOND_STATE_DISABLED:
return "DISABLED";
case BLE_BOND_STATE_IDLE:
return "IDLE";
case BLE_BOND_STATE_STANDBY:
return "STANDBY";
default:
return "UNKNOWN";
}
}
static uint8_t get_bt_stack_peer_id(uint8_t 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)
{
char key[] = MODULE_NAME "/" PEER_ID_KEY;
return settings_save_one(key, &peer_id, sizeof(peer_id));
}
static int store_bt_stack_id_lut(void)
{
char key[] = MODULE_NAME "/" BT_LUT_KEY;
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)
{
struct ble_peer_operation_event *event = new_ble_peer_operation_event();
event->op = op;
event->bt_app_id = app_id;
event->bt_stack_id = get_bt_stack_peer_id(app_id);
APP_EVENT_SUBMIT(event);
}
static void init_bt_stack_id_lut(void)
{
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. */
bond.storage.bt_stack_id_lut[i] = i + 1;
}
}
static bool storage_data_is_valid(void)
{
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",
bond.storage.cur_peer_id_valid, bond.storage.bt_stack_id_lut_valid);
return false;
}
if (bond.storage.cur_peer_id >= BLE_BOND_SLOT_COUNT) {
LOG_WRN("Stored peer id out of range: peer_id=%u max=%u",
bond.storage.cur_peer_id, BLE_BOND_SLOT_COUNT - 1);
return false;
}
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, bond.storage.bt_stack_id_lut[i]);
return false;
}
}
return true;
}
static int settings_set(const char *key, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
ssize_t rc;
if (!strcmp(key, PEER_ID_KEY)) {
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(bond.storage.cur_peer_id));
bond.storage.cur_peer_id_valid = false;
return 0;
}
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(bond.storage.bt_stack_id_lut)) {
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
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,
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);
}
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(ble_bond, MODULE_NAME, NULL, settings_set, NULL, NULL);
static int load_identities(void)
{
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
size_t count = ARRAY_SIZE(addrs);
bt_id_get(addrs, &count);
LOG_INF("Identity count before ensure: %u / %u", (uint32_t)count, CONFIG_BT_ID_MAX);
for (; count < CONFIG_BT_ID_MAX; count++) {
int err = bt_id_create(NULL, NULL);
if (err < 0) {
LOG_ERR("Cannot create identity (err:%d)", err);
return err;
}
LOG_INF("Created identity idx=%u", (uint32_t)count);
}
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);
}
}
static void mark_le_conn_found_cb(struct bt_conn *conn, void *user_data)
{
bool *found = user_data;
ARG_UNUSED(conn);
*found = true;
}
static bool has_any_le_conn(void)
{
bool found = false;
bt_conn_foreach(BT_CONN_TYPE_LE, mark_le_conn_found_cb, &found);
return found;
}
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);
int err;
/* Tell ble_adv to restart advertising session for this identity. */
submit_peer_op_event(PEER_OPERATION_ERASE_ADV, app_id);
err = bt_unpair(stack_id, NULL);
if (err) {
LOG_ERR("Cannot unpair id %u (err:%d)", stack_id, err);
return err;
}
err = bt_id_reset(stack_id, NULL, NULL);
if (err < 0) {
LOG_ERR("Cannot reset id %u (err:%d)", stack_id, err);
return err;
}
submit_peer_op_event(PEER_OPERATION_ERASED, app_id);
return 0;
}
static int erase_all_peers(void)
{
for (uint8_t i = 0; i < BLE_BOND_SLOT_COUNT; i++) {
int err = erase_peer(i);
if (err) {
return err;
}
}
return 0;
}
static bool handle_config_event(const struct config_event *event)
{
if (!event->is_request || (event->recipient != CFG_CHAN_RECIPIENT_LOCAL)) {
return false;
}
if (MOD_FIELD_GET(event->event_id) != BLE_BOND_CFG_MODULE_ID) {
return false;
}
struct config_event *rsp = new_config_event(0);
rsp->transport_id = event->transport_id;
rsp->is_request = false;
rsp->event_id = event->event_id;
rsp->recipient = event->recipient;
rsp->status = CONFIG_STATUS_REJECT;
if (event->status == CONFIG_STATUS_SET) {
uint8_t opt_field = OPT_FIELD_GET(event->event_id);
uint8_t opt_id = (opt_field == 0) ? UINT8_MAX : OPT_ID_GET(opt_field);
switch (opt_id) {
case BLE_BOND_CFG_PEER_SELECT:
if (event->dyndata.size >= 1) {
uint8_t peer_id = event->dyndata.data[0];
if (!select_peer(peer_id)) {
rsp->status = CONFIG_STATUS_SUCCESS;
}
}
break;
case BLE_BOND_CFG_PEER_ERASE:
if (!erase_peer(bond.storage.cur_peer_id)) {
rsp->status = CONFIG_STATUS_SUCCESS;
}
break;
case BLE_BOND_CFG_PEER_ERASE_ALL:
if (!erase_all_peers()) {
rsp->status = CONFIG_STATUS_SUCCESS;
}
break;
default:
break;
}
} else if (event->status == CONFIG_STATUS_FETCH) {
uint8_t opt_field = OPT_FIELD_GET(event->event_id);
uint8_t opt_id = (opt_field == 0) ? UINT8_MAX : OPT_ID_GET(opt_field);
if (opt_id == BLE_BOND_CFG_PEER_SELECT) {
struct config_event *rsp_data = new_config_event(1);
rsp_data->transport_id = event->transport_id;
rsp_data->is_request = false;
rsp_data->event_id = event->event_id;
rsp_data->recipient = event->recipient;
rsp_data->status = CONFIG_STATUS_SUCCESS;
rsp_data->dyndata.data[0] = bond.storage.cur_peer_id;
APP_EVENT_SUBMIT(rsp_data);
return true;
}
}
APP_EVENT_SUBMIT(rsp);
return true;
}
static int init_after_settings_loaded(void)
{
int err = load_identities();
if (err) {
LOG_ERR("Identity initialization failed: %d", err);
return err;
}
if (!storage_data_is_valid()) {
LOG_WRN("Stored BLE bond data invalid, reinitializing defaults");
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(bond.storage.cur_peer_id);
if (err) {
LOG_ERR("Failed to store peer_id=%u (err:%d)",
bond.storage.cur_peer_id, err);
return -EIO;
}
err = store_bt_stack_id_lut();
if (err) {
LOG_ERR("Failed to store bt_stack_id_lut (err:%d)", err);
return -EIO;
}
}
bond.state = BLE_BOND_STATE_IDLE;
LOG_INF("ble_bond init done: state=%s peer_id=%u stack_id=%u",
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;
}
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(settings_loader), MODULE_STATE_READY) &&
(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(bond.state));
module_set_state(MODULE_STATE_ERROR);
}
}
return false;
}
if (is_power_down_event(aeh)) {
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 (bond.state == BLE_BOND_STATE_STANDBY) {
bond.state = BLE_BOND_STATE_IDLE;
module_set_state(MODULE_STATE_READY);
/*
* If a LE link survived suspend, keep it untouched. CAF ble_adv
* treats PEER_OPERATION_SELECTED as a real identity switch and
* will disconnect the current peer. If no link exists, re-emit
* the selection so advertising resumes on the selected slot.
*/
if (!has_any_le_conn()) {
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));
}
__ASSERT_NO_MSG(false);
return false;
}
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);