#include #include #include #include #include #include #define MODULE ble_bond #include #include #include #include #include #include #include #include #include #include "ble_bond_multi_event.h" #include "module_lifecycle.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); #define BLE_SLOT_MIN 1U #define BLE_SLOT_MAX BLE_BOND_MULTI_BLE_SLOT_COUNT #define IDENTITY_DONGLE BLE_BOND_MULTI_DONGLE_SLOT_ID #define SETTINGS_KEY_CURRENT_SLOT "current_slot" #define SETTINGS_KEY_META_PREFIX "meta/" #define DEFAULT_DISPLAY_NAME_EMPTY "Empty" struct ble_bond_multi_slot_meta_storage { uint8_t occupied; bt_addr_le_t last_peer_addr; char display_name[BLE_BOND_MULTI_DISPLAY_NAME_MAX_LEN]; }; struct ble_bond_multi_ctx { struct module_lifecycle_ctx lc; uint8_t current_slot; uint8_t pending_slot; bool current_slot_valid; bool identities_ready; struct ble_bond_multi_slot_meta slot_meta[CONFIG_BT_ID_MAX]; struct bt_conn *active_conn; }; static int do_init(void); static int do_start(void); static int do_stop(void); static int identity_ensure_exists(uint8_t identity); static void slot_bond_cnt_cb(const struct bt_bond_info *info, void *user_data); int ble_bond_multi_select_slot(uint8_t slot); int ble_bond_multi_erase_current_slot(void); const struct ble_bond_multi_slot_meta *ble_bond_multi_get_slot_meta(uint8_t slot); uint8_t ble_bond_multi_get_current_slot(void); static const struct module_lifecycle_cfg lifecycle_cfg = { .mode = ML_MODE_NONE, .stopped_state = MODULE_STATE_OFF, }; static const struct module_lifecycle_ops lifecycle_ops = { .do_init = do_init, .do_start = do_start, .do_stop = do_stop, }; static struct ble_bond_multi_ctx ctx = { .lc = { .state = LC_UNINIT, .cfg = &lifecycle_cfg, .ops = &lifecycle_ops, }, .current_slot = BLE_SLOT_MIN, .pending_slot = BLE_SLOT_MIN, }; BUILD_ASSERT(CONFIG_BT_ID_MAX > IDENTITY_DONGLE, "BT_ID_MAX must include BLE slots and dongle slot"); BUILD_ASSERT(CONFIG_BT_MAX_PAIRED >= 4, "BT_MAX_PAIRED must allow three BLE slots and dongle slot"); static bool is_ble_slot(uint8_t slot) { return (slot >= BLE_SLOT_MIN) && (slot <= BLE_SLOT_MAX); } static void display_name_set_addr(uint8_t slot, const bt_addr_le_t *addr) { struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot]; if ((addr == NULL) || !bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { strncpy(meta->display_name, DEFAULT_DISPLAY_NAME_EMPTY, sizeof(meta->display_name)); meta->display_name[sizeof(meta->display_name) - 1U] = '\0'; return; } bt_addr_le_to_str(addr, meta->display_name, sizeof(meta->display_name)); meta->display_name[sizeof(meta->display_name) - 1U] = '\0'; } static void display_name_set_default(uint8_t slot) { struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot]; const char *name = DEFAULT_DISPLAY_NAME_EMPTY; if (meta->occupied && bt_addr_le_cmp(&meta->last_peer_addr, BT_ADDR_LE_ANY)) { display_name_set_addr(slot, &meta->last_peer_addr); return; } strncpy(meta->display_name, name, sizeof(meta->display_name)); meta->display_name[sizeof(meta->display_name) - 1U] = '\0'; } static void slot_meta_clear(uint8_t slot) { struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot]; memset(meta, 0, sizeof(*meta)); bt_addr_le_copy(&meta->last_peer_addr, BT_ADDR_LE_ANY); display_name_set_default(slot); } static void slot_meta_ensure_name(uint8_t slot) { if (ctx.slot_meta[slot].display_name[0] == '\0') { display_name_set_default(slot); } } static void slot_meta_from_storage(uint8_t slot, const struct ble_bond_multi_slot_meta_storage *storage) { struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot]; meta->occupied = (storage->occupied != 0U); bt_addr_le_copy(&meta->last_peer_addr, &storage->last_peer_addr); memcpy(meta->display_name, storage->display_name, sizeof(meta->display_name)); meta->display_name[sizeof(meta->display_name) - 1U] = '\0'; slot_meta_ensure_name(slot); } static void slot_meta_to_storage(uint8_t slot, struct ble_bond_multi_slot_meta_storage *storage) { const struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot]; memset(storage, 0, sizeof(*storage)); storage->occupied = meta->occupied ? 1U : 0U; bt_addr_le_copy(&storage->last_peer_addr, &meta->last_peer_addr); memcpy(storage->display_name, meta->display_name, sizeof(storage->display_name)); } 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, SETTINGS_KEY_CURRENT_SLOT)) { uint8_t stored_slot; if (len_rd != sizeof(stored_slot)) { return 0; } rc = read_cb(cb_arg, &stored_slot, sizeof(stored_slot)); if (rc == sizeof(stored_slot) && is_ble_slot(stored_slot)) { ctx.current_slot = stored_slot; ctx.pending_slot = stored_slot; ctx.current_slot_valid = true; } return 0; } if (!strncmp(key, SETTINGS_KEY_META_PREFIX, sizeof(SETTINGS_KEY_META_PREFIX) - 1)) { const char *slot_str = key + (sizeof(SETTINGS_KEY_META_PREFIX) - 1); long slot = strtol(slot_str, NULL, 10); struct ble_bond_multi_slot_meta_storage storage; if ((slot < BLE_SLOT_MIN) || (slot > IDENTITY_DONGLE) || (len_rd != sizeof(storage))) { return 0; } rc = read_cb(cb_arg, &storage, sizeof(storage)); if (rc == sizeof(storage)) { slot_meta_from_storage((uint8_t)slot, &storage); } return 0; } return 0; } SETTINGS_STATIC_HANDLER_DEFINE(ble_bond_multi, "ble_multi", NULL, settings_set, NULL, NULL); static int settings_save_current_slot(void) { int err; err = settings_save_one("ble_multi/" SETTINGS_KEY_CURRENT_SLOT, &ctx.current_slot, sizeof(ctx.current_slot)); if (err) { LOG_ERR("Save current slot failed (%d)", err); } return err; } static int settings_save_slot_meta(uint8_t slot) { int err; char key[24]; struct ble_bond_multi_slot_meta_storage storage; slot_meta_to_storage(slot, &storage); snprintk(key, sizeof(key), "ble_multi/" SETTINGS_KEY_META_PREFIX "%u", slot); err = settings_save_one(key, &storage, sizeof(storage)); if (err) { LOG_ERR("Save slot %u meta failed (%d)", slot, err); } return err; } static void active_conn_clear(void) { ctx.active_conn = NULL; } static void bond_addr_get_cb(const struct bt_bond_info *info, void *user_data) { bt_addr_le_t *addr = user_data; if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { bt_addr_le_copy(addr, &info->addr); } } static void publish_state(enum ble_bond_multi_op op) { struct ble_bond_multi_event *event = new_ble_bond_multi_event(); uint8_t occ = 0U; event->current_slot = ctx.current_slot; event->active_identity_id = ctx.current_slot; event->op = op; for (uint8_t slot = BLE_SLOT_MIN; slot <= BLE_SLOT_MAX; slot++) { if (ctx.slot_meta[slot].occupied) { occ |= BIT(slot - BLE_SLOT_MIN); } event->slots[slot - BLE_SLOT_MIN] = ctx.slot_meta[slot]; } event->slot_occupied_bitmap = occ; APP_EVENT_SUBMIT(event); } static int identity_ensure_exists(uint8_t identity) { size_t count = CONFIG_BT_ID_MAX; bt_addr_le_t addrs[CONFIG_BT_ID_MAX]; (void)bt_id_get(addrs, &count); while (count <= identity) { int id = bt_id_create(NULL, NULL); if (id < 0) { LOG_ERR("bt_id_create failed (%d)", id); return id; } count++; } return 0; } static bool slot_has_bond(uint8_t slot) { size_t cnt = 0; bt_foreach_bond(slot, slot_bond_cnt_cb, &cnt); return cnt > 0U; } static void slot_bond_cnt_cb(const struct bt_bond_info *info, void *user_data) { size_t *count = user_data; ARG_UNUSED(info); (*count)++; } static void slot_update_from_bonds(uint8_t slot) { struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot]; meta->occupied = slot_has_bond(slot); if (!meta->occupied) { bt_addr_le_copy(&meta->last_peer_addr, BT_ADDR_LE_ANY); } else if (!bt_addr_le_cmp(&meta->last_peer_addr, BT_ADDR_LE_ANY)) { bt_foreach_bond(slot, bond_addr_get_cb, &meta->last_peer_addr); } if (meta->occupied && ((meta->display_name[0] == '\0') || !strcmp(meta->display_name, DEFAULT_DISPLAY_NAME_EMPTY))) { display_name_set_addr(slot, &meta->last_peer_addr); } slot_meta_ensure_name(slot); } static void all_slots_refresh_from_bonds(void) { for (uint8_t slot = BLE_SLOT_MIN; slot <= IDENTITY_DONGLE; slot++) { slot_update_from_bonds(slot); } } static void submit_selected_event(uint8_t identity) { struct ble_peer_operation_event *event = new_ble_peer_operation_event(); event->op = PEER_OPERATION_SELECTED; event->bt_app_id = identity; event->bt_stack_id = identity; APP_EVENT_SUBMIT(event); } static void submit_erased_event(uint8_t identity) { struct ble_peer_operation_event *event = new_ble_peer_operation_event(); event->op = PEER_OPERATION_ERASED; event->bt_app_id = identity; event->bt_stack_id = identity; APP_EVENT_SUBMIT(event); } static int switch_slot(uint8_t slot) { if (!is_ble_slot(slot) || (slot == ctx.current_slot)) { return 0; } ctx.pending_slot = slot; ctx.current_slot = slot; submit_selected_event(slot); return settings_save_current_slot(); } static int erase_slot(uint8_t slot) { int err; err = bt_unpair(slot, BT_ADDR_LE_ANY); if (err) { LOG_ERR("bt_unpair slot %u failed (%d)", slot, err); return err; } slot_meta_clear(slot); err = settings_save_slot_meta(slot); if (err) { return err; } submit_erased_event(slot); submit_selected_event(slot); return 0; } static int do_init(void) { for (uint8_t slot = BLE_SLOT_MIN; slot <= IDENTITY_DONGLE; slot++) { slot_meta_clear(slot); } if (!ctx.current_slot_valid) { ctx.current_slot = BLE_SLOT_MIN; ctx.pending_slot = BLE_SLOT_MIN; } active_conn_clear(); return 0; } static int do_start(void) { int err; for (uint8_t identity = BLE_SLOT_MIN; identity <= IDENTITY_DONGLE; identity++) { err = identity_ensure_exists(identity); if (err) { return err; } } ctx.identities_ready = true; all_slots_refresh_from_bonds(); for (uint8_t slot = BLE_SLOT_MIN; slot <= IDENTITY_DONGLE; slot++) { (void)settings_save_slot_meta(slot); } submit_selected_event(ctx.current_slot); publish_state(BLE_BOND_MULTI_OP_REFRESH); return 0; } static int do_stop(void) { active_conn_clear(); return 0; } static bool handle_ble_peer_event(const struct ble_peer_event *event) { switch (event->state) { case PEER_STATE_CONNECTED: ctx.active_conn = event->id; return false; case PEER_STATE_SECURED: if (ctx.active_conn != event->id) { return false; } ctx.slot_meta[ctx.current_slot].occupied = true; bt_addr_le_copy(&ctx.slot_meta[ctx.current_slot].last_peer_addr, bt_conn_get_dst(ctx.active_conn)); if ((ctx.slot_meta[ctx.current_slot].display_name[0] == '\0') || !strcmp(ctx.slot_meta[ctx.current_slot].display_name, DEFAULT_DISPLAY_NAME_EMPTY)) { display_name_set_addr(ctx.current_slot, &ctx.slot_meta[ctx.current_slot] .last_peer_addr); } (void)settings_save_slot_meta(ctx.current_slot); publish_state(BLE_BOND_MULTI_OP_REFRESH); return false; case PEER_STATE_DISCONNECTED: case PEER_STATE_CONN_FAILED: if (ctx.active_conn == event->id) { active_conn_clear(); } return false; default: return false; } } 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)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); return false; } return false; } if (is_ble_peer_event(aeh)) { return handle_ble_peer_event(cast_ble_peer_event(aeh)); } if (is_ble_bond_multi_event(aeh)) { return false; } return false; } int ble_bond_multi_select_slot(uint8_t slot) { int err; if (!module_lifecycle_is_running(&ctx.lc)) { return -EAGAIN; } if (!ctx.identities_ready) { return -EAGAIN; } err = switch_slot(slot); if (!err) { publish_state(BLE_BOND_MULTI_OP_SWITCH); } return err; } int ble_bond_multi_erase_current_slot(void) { int err; if (!module_lifecycle_is_running(&ctx.lc)) { return -EAGAIN; } if (!ctx.identities_ready) { return -EAGAIN; } err = erase_slot(ctx.current_slot); if (!err) { publish_state(BLE_BOND_MULTI_OP_ERASE); } return err; } const struct ble_bond_multi_slot_meta *ble_bond_multi_get_slot_meta(uint8_t slot) { if (!is_ble_slot(slot)) { return NULL; } return &ctx.slot_meta[slot]; } uint8_t ble_bond_multi_get_current_slot(void) { return ctx.current_slot; } APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);