精华内容
下载资源
问答
  • 此篇文章简单分析一下蓝牙解除配对在协议栈中的工作流程。分析的协议栈版本是Android8.0 协议栈的接口都定义在bluetooth.cc这个文件中: static int remove_bond(const bt_bdaddr_t* bd_addr) { if (is_...

    此篇文章简单分析一下蓝牙解除配对在协议栈中的工作流程。分析的协议栈版本是Android8.0

    协议栈的接口都定义在bluetooth.cc这个文件中:

    static int remove_bond(const bt_bdaddr_t* bd_addr) {
      if (is_restricted_mode() && !btif_storage_is_restricted_device(bd_addr))
        return BT_STATUS_SUCCESS;
      /* sanity check */
      if (interface_ready() == false) return BT_STATUS_NOT_READY;
      return btif_dm_remove_bond(bd_addr);
    }

    这里需要注意一下bt_bdaddr_t 是一个结构体,内部一个元素是数组。

     /** Bluetooth Address */
     typedef struct {
        uint8_t address[6];
     } __attribute__((packed))bt_bdaddr_t;

    进入btif_dm_remove_bond:

    bt_status_t btif_dm_remove_bond(const bt_bdaddr_t* bd_addr) {
      bdstr_t bdstr;
      btif_transfer_context(btif_dm_generic_evt, BTIF_DM_CB_REMOVE_BOND,
                            (char*)bd_addr, sizeof(bt_bdaddr_t), NULL);
      return BT_STATUS_SUCCESS;
    }

    这个函数btif_transfer_context 是将remove bond这件事情交给bt_jni_workqueue_thread来处理。在该线程中执行的函数就是btif_dm_generic_evt

    static void btif_dm_generic_evt(uint16_t event, char* p_param) {
      BTIF_TRACE_EVENT("%s: event=%d", __func__, event);
      switch (event) {
        case BTIF_DM_CB_REMOVE_BOND: {
          btif_dm_cb_remove_bond((bt_bdaddr_t*)p_param);
        } break;
      }
    }

    执行的函数:btif_dm_cb_remove_bond 

    void btif_dm_cb_remove_bond(bt_bdaddr_t* bd_addr) {
        BTA_DmRemoveDevice((uint8_t*)bd_addr->address);
    }

    函数执行到了BTA层面。

    tBTA_STATUS BTA_DmRemoveDevice(BD_ADDR bd_addr) {
      tBTA_DM_API_REMOVE_DEVICE* p_msg =
       (tBTA_DM_API_REMOVE_DEVICE*)osi_calloc(sizeof(tBTA_DM_API_REMOVE_DEVICE));
      p_msg->hdr.event = BTA_DM_API_REMOVE_DEVICE_EVT;
      bdcpy(p_msg->bd_addr, bd_addr);
      bta_sys_sendmsg(p_msg);
      return BTA_SUCCESS;
    }

    这边是发送了一个BTA_DM_API_REMOVE_DEVICE_EVT到另一个线程:bt_workqueue_thread,这个线程是专门处理bt里面的队列的,当队列里面有数据都会在这个线程里面处理。

    通过bta_sys_sendmsg发送的信号都会经过bta_sys_event来处理,bta_sys_event会根据相应的事件路由到相应的处理函数。这里处理这个事件的函数是:

    /*******************************************************************************
     *
     * Function         bta_dm_remove_device
     *
     * Description      Removes device, disconnects ACL link if required.
     ***
     ******************************************************************************/
    void bta_dm_remove_device(tBTA_DM_MSG* p_data) {
      tBTA_DM_API_REMOVE_DEVICE* p_dev = &p_data->remove_dev;//获取消息
      bool continue_delete_other_dev = false;/* If ACL exists for the device in the remove_bond message*/
      bool continue_delete_dev = false;
      uint8_t other_transport = BT_TRANSPORT_INVALID;
    /*首先判断该address 是否存有link*/
      if (BTM_IsAclConnectionUp(p_dev->bd_addr, BT_TRANSPORT_LE) ||
          BTM_IsAclConnectionUp(p_dev->bd_addr, BT_TRANSPORT_BR_EDR)) {
        APPL_TRACE_DEBUG("%s: ACL Up count  %d", __func__,
                         bta_dm_cb.device_list.count);
        continue_delete_dev = false;
        /* Take the link down first, and mark the device for removal when
         * disconnected */
        for (int i = 0; i < bta_dm_cb.device_list.count; i++) {
          if (!bdcmp(bta_dm_cb.device_list.peer_device[i].peer_bdaddr,
                     p_dev->bd_addr)) {
            uint8_t transport = BT_TRANSPORT_BR_EDR;
            transport = bta_dm_cb.device_list.peer_device[i].transport;
            bta_dm_cb.device_list.peer_device[i].conn_state = BTA_DM_UNPAIRING;//设置标志位,在acl link状态改变的函数中会去删除link key
            btm_remove_acl(p_dev->bd_addr, transport);//已经存在link,那么要先删除这条linkbreak;
          }
        }
      } else {
        continue_delete_dev = true;
      }
    ...
      /* Delete the device mentioned in the msg */
      if (continue_delete_dev) bta_dm_process_remove_device(p_dev->bd_addr);//解配的设备没有处于连接状态则执行
    }

    从上面的代码可以看出,核心的地方就两处:

    1. bta_dm_cb.device_list.peer_device[i].conn_state = BTA_DM_UNPAIRING; 标记该设备是要解除配对的,后续会删除其link key
    2. btm_remove_acl 做实际的断开连接的操作。

     现在简单看看btm_remove_acl 的实现:

    tBTM_STATUS btm_remove_acl(BD_ADDR bd_addr, tBT_TRANSPORT transport) {
      uint16_t hci_handle = BTM_GetHCIConnHandle(bd_addr, transport);
      tBTM_STATUS status = BTM_SUCCESS;
    ...
      {
        if (hci_handle != 0xFFFF && p_dev_rec &&
            p_dev_rec->sec_state != BTM_SEC_STATE_DISCONNECTING) {
          btsnd_hcic_disconnect(hci_handle, HCI_ERR_PEER_USER);
        } else
          status = BTM_UNKNOWN_ADDR;
      }
      return status;
    }

    先搜索btm_cb.acl_db 找到该地址对应的ACL link 的控制体,获取该link 的handle,该handle用于标志是哪一条link,然后调用btsnd_hcic_disconnect(hci_handle, HCI_ERR_PEER_USER); 向controller发送断开的命令。

    到这里,在bt_workqueue_thread 线程中发送断开link 的消息是结束了,controller收到信息处理之后,会有断开完成事件上来,这个时候处理该事件的线程依然是bt_workqueue_thread,我们看看具体的处理过程:处理底层的事件的接口实现在btu_hcif.cc中:

    /*******************************************************************************
     *
     * Function         btu_hcif_disconnection_comp_evt
     *
     * Description      Process event HCI_DISCONNECTION_COMP_EVT
     *
     * Returns          void
     *
     ******************************************************************************/
    static void btu_hcif_disconnection_comp_evt(uint8_t* p) {
      uint16_t handle;
      uint8_t reason;
      ++p;
      STREAM_TO_UINT16(handle, p);
      STREAM_TO_UINT8(reason, p);
      handle = HCID_GET_HANDLE(handle);
      l2c_link_hci_disc_comp(handle, reason);
      /* Notify security manager */
      btm_sec_disconnected(handle, reason);
    }

    从函数的注释我们也能看出其是处理HCI_DISCONNECTION_COMP_EVT 这个事件的。从代码流程中,我们能看到代码主要做了两件事:

    1. l2c_link_hci_disc_comp
    2. btm_sec_disconnected,这个函数是用来通知security manager的。

    下面依次看看这两个函数的实现:

    bool l2c_link_hci_disc_comp(uint16_t handle, uint8_t reason) {
      L2CAP_TRACE_EVENT("entry l2c_link_hci_disc_comp libs_liu");
      tL2C_LCB* p_lcb;
      tL2C_CCB* p_ccb;
      bool status = true;
      bool lcb_is_free = true;
      tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR;
      /* See if we have a link control block for the connection */
      p_lcb = l2cu_find_lcb_by_handle(handle);
    ...
     {
        /* Just in case app decides to try again in the callback context */
        p_lcb->link_state = LST_DISCONNECTING;
    
        /* Check for BLE and handle that differently */
        if (p_lcb->transport == BT_TRANSPORT_LE)
          btm_ble_update_link_topology_mask(p_lcb->link_role, false);
        /* Link is disconnected. For all channels, send the event through */
        /* their FSMs. The CCBs should remove themselves from the LCB     */
        for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb;) {
          tL2C_CCB* pn = p_ccb->p_next_ccb;
          /* Keep connect pending control block (if exists)
           * Possible Race condition when a reconnect occurs
           * on the channel during a disconnect of link. This
           * ccb will be automatically retried after link disconnect
           * arrives
           */
          if (p_ccb != p_lcb->p_pending_ccb) {
            l2c_csm_execute(p_ccb, L2CEVT_LP_DISCONNECT_IND, &reason);
          }
          p_ccb = pn;
        }
        p_lcb->p_pending_ccb = NULL;
    
        /* Release the LCB */
        if (lcb_is_free){ 
        l2cu_release_lcb(p_lcb);}
      }
    ...
      return status;
    }

    上面函数核心的地方 是释放link 相关的结构体l2cu_release_lcb,这个函数主要就是释放一些定时器,内存以及channel结构释放,另外还做一些通知的事情:

          /* If anyone cares, tell him database changed */
          if (btm_cb.p_bl_changed_cb) {
            evt_data.event = BTM_BL_DISCN_EVT;
            evt_data.discn.p_bda = bda;
            evt_data.discn.handle = p->hci_handle;
            evt_data.discn.transport = p->transport;
            (*btm_cb.p_bl_changed_cb)(&evt_data);
          }

    这里需要思考一下,这个btm_cb.p_bl_changed_cb 回调函数是哪里注册。这个函数其实在bta_dm_sys_hw_cback 函数中注册的:

    BTM_RegBusyLevelNotif(bta_dm_bl_change_cback, NULL,
                              BTM_BL_UPDATE_MASK | BTM_BL_ROLE_CHG_MASK);
    //那其实是
    btm_cb.p_bl_changed_cb  = bta_dm_bl_change_cback

    那我们现在看看这个busy level change的实现:从上面也可以看出是把BTM_BL_DISCN_EVT 传入到 bta_dm_bl_change_cback 来执行:

    static void bta_dm_bl_change_cback(tBTM_BL_EVENT_DATA* p_data) {
      tBTA_DM_ACL_CHANGE* p_msg =
          (tBTA_DM_ACL_CHANGE*)osi_malloc(sizeof(tBTA_DM_ACL_CHANGE));
    
      p_msg->event = p_data->event;
      p_msg->is_new = false;
    
      switch (p_msg->event) {
        case BTM_BL_CONN_EVT:
          p_msg->is_new = true;
          bdcpy(p_msg->bd_addr, p_data->conn.p_bda);
          p_msg->transport = p_data->conn.transport;
          p_msg->handle = p_data->conn.handle;
          break;
        case BTM_BL_DISCN_EVT:
          bdcpy(p_msg->bd_addr, p_data->discn.p_bda);
          p_msg->transport = p_data->discn.transport;
          p_msg->handle = p_data->discn.handle;
          break;
        case BTM_BL_UPDATE_EVT:
          p_msg->busy_level = p_data->update.busy_level;
          p_msg->busy_level_flags = p_data->update.busy_level_flags;
          break;
        case BTM_BL_ROLE_CHG_EVT:
          p_msg->new_role = p_data->role_chg.new_role;
          p_msg->hci_status = p_data->role_chg.hci_status;
          bdcpy(p_msg->bd_addr, p_data->role_chg.p_bda);
          break;
        case BTM_BL_COLLISION_EVT:
          bdcpy(p_msg->bd_addr, p_data->conn.p_bda);
          break;
      }
    
      p_msg->hdr.event = BTA_DM_ACL_CHANGE_EVT;
      bta_sys_sendmsg(p_msg);
    }

    从这个函数的实现来看,他是可以处理很多的event的。然后统一发送给bt_workqueue_thread中的另外一个队列中,看看具体的处理流程:

    其实现的函数是在bta_dm_act.cc中,

    void bta_dm_acl_change(tBTA_DM_MSG* p_data) {
      uint8_t i;
      uint8_t* p;
      tBTA_DM_SEC conn;
      bool is_new = p_data->acl_change.is_new;
      BD_ADDR_PTR p_bda = p_data->acl_change.bd_addr;
      bool need_policy_change = false;
      bool issue_unpair_cb = false;
    
      tBTA_DM_PEER_DEVICE* p_dev;
      memset(&conn, 0, sizeof(tBTA_DM_SEC));
    ...
        for (i = 0; i < bta_dm_cb.device_list.count; i++) {
          if (bdcmp(bta_dm_cb.device_list.peer_device[i].peer_bdaddr, p_bda) ||
              bta_dm_cb.device_list.peer_device[i].transport !=
                  p_data->acl_change.transport)
            continue;
        /*找到之后*/
          if (bta_dm_cb.device_list.peer_device[i].conn_state == BTA_DM_UNPAIRING) {//之前已经标记过
            if (BTM_SecDeleteDevice(bta_dm_cb.device_list.peer_device[i].peer_bdaddr))
          BTA_GATTC_Refresh(p_bda);//remove all gatt information issue_unpair_cb
    = true; } conn.link_down.is_removed = bta_dm_cb.device_list.peer_device[i].remove_dev_pending; break; } ... conn.link_down.link_type = p_data->acl_change.transport; ... bdcpy(conn.link_down.bd_addr, p_bda); conn.link_down.status = (uint8_t)btm_get_acl_disc_reason_code(); if (bta_dm_cb.p_sec_cback) { //执行这里的函数 bta_dm_cb.p_sec_cback(BTA_DM_LINK_DOWN_EVT, &conn); if (issue_unpair_cb) bta_dm_cb.p_sec_cback(BTA_DM_DEV_UNPAIRED_EVT, &conn); } bta_dm_adjust_roles(true); }

    上面的回调函数主要执行如下:

    1. 从bta_dm_cb 查找到该地址的结构。
    2. 找到之后判断bta_dm_cb.device_list.peer_device[i].conn_state的状态,如果是BTA_DM_UNPAIRING,则开始删除link key:BTM_SecDeleteDevice
    3. 回调bta_dm_cb.p_sec_cback :BTA_DM_LINK_DOWN_EVT以及BTA_DM_DEV_UNPAIRED_EVT

    前两个容易理解,看看第三点:

    第三点的回调函数在enable 的时候就已经注册好了:bta_dm_cb.p_sec_cback = bte_dm_evt ,我们进一步看看:

    void bte_dm_evt(tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) {
      bt_status_t status = btif_transfer_context(
          btif_dm_upstreams_evt, (uint16_t)event, (char*)p_data,
          sizeof(tBTA_DM_SEC), btif_dm_data_copy);
    }

    发现其实 他实质执行的函数是btif_dm_upstreams_evt ,这个函数处理的event非常的多,这里就列出相关的代码:

    static void btif_dm_upstreams_evt(uint16_t event, char* p_param) {
      tBTA_DM_SEC* p_data = (tBTA_DM_SEC*)p_param;
      tBTA_SERVICE_MASK service_mask;
      uint32_t i;
      bt_bdaddr_t bd_addr;
    
      switch (event) {
    ...
        case BTA_DM_DEV_UNPAIRED_EVT:
          bdcpy(bd_addr.address, p_data->link_down.bd_addr);
          btm_set_bond_type_dev(p_data->link_down.bd_addr, BOND_TYPE_UNKNOWN);
    
    /*special handling for HID devices */
    #if (defined(BTA_HH_INCLUDED) && (BTA_HH_INCLUDED == true))
          btif_hh_remove_device(bd_addr);
    #endif
    #if (defined(BTA_HD_INCLUDED) && (BTA_HD_INCLUDED == TRUE))
          btif_hd_remove_device(bd_addr);
    #endif
          btif_storage_remove_bonded_device(&bd_addr);
          bond_state_changed(BT_STATUS_SUCCESS, &bd_addr, BT_BOND_STATE_NONE);
          break;
    
        case BTA_DM_LINK_DOWN_EVT:
          bdcpy(bd_addr.address, p_data->link_down.bd_addr);
          btm_set_bond_type_dev(p_data->link_down.bd_addr, BOND_TYPE_UNKNOWN);
          btif_av_move_idle(bd_addr);
          BTIF_TRACE_DEBUG(
              "BTA_DM_LINK_DOWN_EVT. Sending BT_ACL_STATE_DISCONNECTED");
          HAL_CBACK(bt_hal_cbacks, acl_state_changed_cb, BT_STATUS_SUCCESS,
                    &bd_addr, BT_ACL_STATE_DISCONNECTED);
          break;
      }
    
      btif_dm_data_free(event, p_data);
    }

    处理的两个事件是BTA_DM_LINK_DOWN_EVT和BTA_DM_DEV_UNPAIRED_EVT 

    其中BTA_DM_LINK_DOWN_EVT 主要是执行acl_state_changed_cb,向上层传导BT_ACL_STATE_DISCONNECTED

    这里主要看一下BTA_DM_DEV_UNPAIRED_EVT  的处理:

    1. btif_hh_remove_device   
    2. btif_storage_remove_bonded_device  删除config中的key 的信息。

    下面看看btif_hh_remove_device

     

    void btif_hh_remove_device(bt_bdaddr_t bd_addr) {
      int i;
      btif_hh_device_t* p_dev;
      btif_hh_added_device_t* p_added_dev;for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) {
        p_added_dev = &btif_hh_cb.added_devices[i];
        if (memcmp(&(p_added_dev->bd_addr), &bd_addr, 6) == 0) {
          BTA_HhRemoveDev(p_added_dev->dev_handle);//remove dev
          btif_storage_remove_hid_info(&(p_added_dev->bd_addr));//删除config中的hid信息
          memset(&(p_added_dev->bd_addr), 0, 6);
          p_added_dev->dev_handle = BTA_HH_INVALID_HANDLE;
          break;
        }
      }
    ... p_dev
    = btif_hh_find_dev_by_bda(&bd_addr);/* need to notify up-layer device is disconnected to avoid state out of sync * with up-layer */ HAL_CBACK(bt_hh_callbacks, connection_state_cb, &(p_dev->bd_addr),//向上层汇报状态 BTHH_CONN_STATE_DISCONNECTED); p_dev->dev_status = BTHH_CONN_STATE_UNKNOWN; p_dev->dev_handle = BTA_HH_INVALID_HANDLE; p_dev->ready_for_data = false; ...if (p_dev->fd >= 0) { bta_hh_co_destroy(p_dev->fd);//销毁相关的结构 p_dev->fd = -1; } }

    上面代码很简单,主要做清理工作:下面具体看看BTA_HhRemoveDev

    void BTA_HhRemoveDev(uint8_t dev_handle) {
      tBTA_HH_MAINT_DEV* p_buf =
          (tBTA_HH_MAINT_DEV*)osi_calloc(sizeof(tBTA_HH_MAINT_DEV));
      p_buf->hdr.event = BTA_HH_API_MAINT_DEV_EVT;
      p_buf->sub_event = BTA_HH_RMV_DEV_EVT;
      p_buf->hdr.layer_specific = (uint16_t)dev_handle;
      bta_sys_sendmsg(p_buf);
    }

    发送了一个BTA_HH_API_MAINT_DEV_EVT的事件到达BTU task,

    void bta_hh_maint_dev_act(tBTA_HH_DEV_CB* p_cb, tBTA_HH_DATA* p_data) {
      tBTA_HH_MAINT_DEV* p_dev_info = &p_data->api_maintdev;
      tBTA_HH_DEV_INFO dev_info;
      uint8_t dev_handle;
    
      dev_info.status = BTA_HH_ERR;
      dev_info.handle = BTA_HH_INVALID_HANDLE;
    
      switch (p_dev_info->sub_event) {
    ...
        case BTA_HH_RMV_DEV_EVT: /* remove device */
          dev_info.handle = (uint8_t)p_dev_info->hdr.layer_specific;
          bdcpy(dev_info.bda, p_cb->addr);
    
          if (p_cb->is_le_device) {//le 设备
            bta_hh_le_remove_dev_bg_conn(p_cb);//移除background 里面的list,让其不要回连
            bta_hh_sm_execute(p_cb, BTA_HH_API_CLOSE_EVT, NULL);
            bta_hh_clean_up_kdev(p_cb);
          } else//bredr 设备
          {
            if (HID_HostRemoveDev(dev_info.handle) == HID_SUCCESS) {
              dev_info.status = BTA_HH_OK;
              /* remove from known device list in BTA */
              bta_hh_clean_up_kdev(p_cb);
            }
          }
          break;
      }
      (*bta_hh_cb.p_cback)(p_dev_info->sub_event, (tBTA_HH*)&dev_info);
    }

    这里先讲一下bta_hh_cb.p_cback,也是在 BTA_HhEnable(BTUI_HH_SECURITY, bte_hh_evt); 时候注册的,那么也就是说bta_hh_cb.p_cback = bte_hh_evt

    void bte_hh_evt(tBTA_HH_EVT event, tBTA_HH* p_data) {
      bt_status_t status;
      int param_len = 0;
    
      if (BTA_HH_ENABLE_EVT == event)
        param_len = sizeof(tBTA_HH_STATUS);
      else if (BTA_HH_OPEN_EVT == event)
        param_len = sizeof(tBTA_HH_CONN);
      else if (BTA_HH_DISABLE_EVT == event)
        param_len = sizeof(tBTA_HH_STATUS);
      else if (BTA_HH_CLOSE_EVT == event)
        param_len = sizeof(tBTA_HH_CBDATA);
      else if (BTA_HH_GET_DSCP_EVT == event)
        param_len = sizeof(tBTA_HH_DEV_DSCP_INFO);
      else if ((BTA_HH_GET_PROTO_EVT == event) || (BTA_HH_GET_RPT_EVT == event) ||
               (BTA_HH_GET_IDLE_EVT == event))
        param_len = sizeof(tBTA_HH_HSDATA);
      else if ((BTA_HH_SET_PROTO_EVT == event) || (BTA_HH_SET_RPT_EVT == event) ||
               (BTA_HH_VC_UNPLUG_EVT == event) || (BTA_HH_SET_IDLE_EVT == event))
        param_len = sizeof(tBTA_HH_CBDATA);
      else if ((BTA_HH_ADD_DEV_EVT == event) || (BTA_HH_RMV_DEV_EVT == event))
        param_len = sizeof(tBTA_HH_DEV_INFO);
      else if (BTA_HH_API_ERR_EVT == event)
        param_len = 0;
      /* switch context to btif task context (copy full union size for convenience)
       */
      status = btif_transfer_context(btif_hh_upstreams_evt, (uint16_t)event,
                                     (char*)p_data, param_len, NULL);
    
      /* catch any failed context transfers */
      ASSERTC(status == BT_STATUS_SUCCESS, "context transfer failed", status);
    }

    其本质还是transfer 到btif 线程,执行btif_hh_upstreams_evt 函数,这边追踪了一下该事件的处理,只是打印了一些log:

        case BTA_HH_RMV_DEV_EVT:
          BTIF_TRACE_DEBUG("BTA_HH_RMV_DEV_EVT: status = %d, handle = %d",
                           p_data->dev_info.status, p_data->dev_info.handle);
          BTIF_TRACE_DEBUG("BTA_HH_RMV_DEV_EVT:bda = %02x:%02x:%02x:%02x:%02x:%02x",
                           p_data->dev_info.bda[0], p_data->dev_info.bda[1],
                           p_data->dev_info.bda[2], p_data->dev_info.bda[3],
                           p_data->dev_info.bda[4], p_data->dev_info.bda[5]);
          break;

    现在着重看一下bta_hh_le_remove_dev_bg_conn(p_cb) 的处理

    void bta_hh_le_remove_dev_bg_conn(tBTA_HH_DEV_CB* p_dev_cb) {
      if (p_dev_cb->in_bg_conn) {
        p_dev_cb->in_bg_conn = false;
        BTA_GATTC_CancelOpen(bta_hh_cb.gatt_if, p_dev_cb->addr, false);
      }
    
      /* deregister all notifications */
      bta_hh_le_deregister_input_notif(p_dev_cb);
    }
    void BTA_GATTC_CancelOpen(tBTA_GATTC_IF client_if, BD_ADDR remote_bda,
                              bool is_direct) {
      tBTA_GATTC_API_CANCEL_OPEN* p_buf = (tBTA_GATTC_API_CANCEL_OPEN*)osi_malloc(
          sizeof(tBTA_GATTC_API_CANCEL_OPEN));
    
      p_buf->hdr.event = BTA_GATTC_API_CANCEL_OPEN_EVT;
      p_buf->client_if = client_if;
      p_buf->is_direct = is_direct;
      memcpy(p_buf->remote_bda, remote_bda, BD_ADDR_LEN);
    
      bta_sys_sendmsg(p_buf);
    }
    void bta_gattc_process_api_open_cancel(tBTA_GATTC_DATA* p_msg) {
      uint16_t event = ((BT_HDR*)p_msg)->event;
      tBTA_GATTC_CLCB* p_clcb = NULL;
      tBTA_GATTC_RCB* p_clreg;
      tBTA_GATTC cb_data;
    
      if (p_msg->api_cancel_conn.is_direct) {
    ...
      } else {
        bta_gattc_cancel_bk_conn(&p_msg->api_cancel_conn);
      }
    }
    void bta_gattc_cancel_bk_conn(tBTA_GATTC_API_CANCEL_OPEN* p_data) {
      tBTA_GATTC_RCB* p_clreg;
      tBTA_GATTC cb_data;
      cb_data.status = BTA_GATT_ERROR;
    
      /* remove the device from the bg connection mask */
      if (bta_gattc_mark_bg_conn(p_data->client_if, p_data->remote_bda, false)) {
        if (GATT_CancelConnect(p_data->client_if, p_data->remote_bda, false)) {
          cb_data.status = BTA_GATT_OK;
        } else {
          APPL_TRACE_ERROR("bta_gattc_cancel_bk_conn failed");
        }
      }
      p_clreg = bta_gattc_cl_get_regcb(p_data->client_if);
    
      if (p_clreg && p_clreg->p_cback) {
        (*p_clreg->p_cback)(BTA_GATTC_CANCEL_OPEN_EVT, &cb_data);
      }
    }
    bool GATT_CancelConnect(tGATT_IF gatt_if, BD_ADDR bd_addr, bool is_direct) {
      tGATT_REG* p_reg;
      tGATT_TCB* p_tcb;
      bool status = true;
      tGATT_IF temp_gatt_if;
      uint8_t start_idx, found_idx;
    
        if (!gatt_if) 
            ...
            else {
          status = gatt_remove_bg_dev_for_app(gatt_if, bd_addr);
        }
      return status;
    }
    bool gatt_remove_bg_dev_for_app(tGATT_IF gatt_if, BD_ADDR bd_addr) {
      tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE);
      bool status;
    
      if (p_tcb) gatt_update_app_use_link_flag(gatt_if, p_tcb, false, false);
      status = gatt_update_auto_connect_dev(gatt_if, false, bd_addr);
      return status;
    }
    bool gatt_update_auto_connect_dev(tGATT_IF gatt_if, bool add, BD_ADDR bd_addr) {
    ...
      if (add) {
        ret = gatt_add_bg_dev_list(p_reg, bd_addr);
        if (ret && p_tcb != NULL) {
          /* if a connected device, update the link holding number */
          gatt_update_app_use_link_flag(gatt_if, p_tcb, true, true);
        }
      } else {
        ret = gatt_remove_bg_dev_from_list(p_reg, bd_addr);
      }
      return ret;
    }
    bool gatt_remove_bg_dev_from_list(tGATT_REG* p_reg, BD_ADDR bd_addr) {
      tGATT_IF gatt_if = p_reg->gatt_if;
      tGATT_BG_CONN_DEV* p_dev = NULL;
      uint8_t i, j;
      bool ret = false;
    
      p_dev = gatt_find_bg_dev(bd_addr);
      if (p_dev == NULL) {
        return ret;
      }
    
      for (i = 0; i < GATT_MAX_APPS && (p_dev->gatt_if[i] > 0); i++) {
        if (p_dev->gatt_if[i] == gatt_if) {
          p_dev->gatt_if[i] = 0;
          /* move all element behind one forward */
          for (j = i + 1; j < GATT_MAX_APPS; j++)
            p_dev->gatt_if[j - 1] = p_dev->gatt_if[j];
    
          if (p_dev->gatt_if[0] == 0){//只有设备全部都断开了才会去做BTM_BleUpdateBgConnDev
            ret = BTM_BleUpdateBgConnDev(false, p_dev->remote_bda);
              }
          else
            ret = true;
    
          break;
        }
      }
    
      if (i != GATT_MAX_APPS && p_dev->gatt_if[0] == 0) {
        memset(p_dev, 0, sizeof(tGATT_BG_CONN_DEV));
      }
    
      return ret;
    }
    bool BTM_BleUpdateBgConnDev(bool add_remove, BD_ADDR remote_bda) {
      return btm_update_dev_to_white_list(add_remove, remote_bda);
    }
    bool btm_update_dev_to_white_list(bool to_add, BD_ADDR bd_addr) {
      tBTM_BLE_CB* p_cb = &btm_cb.ble_ctr_cb;
    
      if (to_add && p_cb->white_list_avail_size == 0) {
        BTM_TRACE_ERROR("%s Whitelist full, unable to add device", __func__);
        return false;
      }
      if (to_add)
        background_connection_add((bt_bdaddr_t*)bd_addr);
      else{
        background_connection_remove((bt_bdaddr_t*)bd_addr);
      }
      btm_suspend_wl_activity(p_cb->wl_state);
      btm_enq_wl_dev_operation(to_add, bd_addr);
      btm_resume_wl_activity(p_cb->wl_state);
      return true;
    }

    删除 里面的值: background_connection_remove

    static void btm_resume_wl_activity(tBTM_BLE_WL_STATE wl_state) {
      btm_ble_resume_bg_conn();
      if (wl_state & BTM_BLE_WL_ADV) {
        btm_ble_start_adv();
      }
    }
    bool btm_ble_resume_bg_conn(void) {
      tBTM_BLE_CB* p_cb = &btm_cb.ble_ctr_cb;
      if (p_cb->bg_conn_type == BTM_BLE_CONN_AUTO) {
        return btm_ble_start_auto_conn(true);//如果background 里面还存在设备,那么就会回连
      }
      return false;
    }

    这个解除绑定的流程 暂时是分析到这里。

     

     

    转载于:https://www.cnblogs.com/libs-liu/p/9193864.html

    展开全文
  • GAP Bond管理和LE安全连接 GAP Bond Manager是一个可配置的模块,使用Bond manager后应用程序可以减少大部分安全机制。下表列出了术语。 术语 描述 配对(Pairing) 交换密钥的过程 加密(Encryption...
    公司主页 文档归类淘宝

    GAP Bond管理和LE安全连接

    GAP Bond Manager是一个可配置的模块,使用Bond manager后应用程序可以减少大部分安全机制。下表列出了术语。

    术语 描述
    配对(Pairing) 交换密钥的过程
    加密(Encryption) 0x02
    认证(Authentication) 使用中间人(MITM)保护完成的配对
    Bonding 将密钥存储在非易失性存储器中
    授权(Authorization) 除了认证之外,还需要额外的应用级密钥交换
    OOB(Out of band) 密钥不是通过空中交换,而是通过串行端口或NFC等其他来源进行交换。这也提供了MITM保护。
    MITM(Man in the Middle protection) 这可以防止攻击者收听通过空中传输的密钥来破坏加密。
    只是工作(Just work) 配对方法,其中密钥在没有MITM的情况下通过空中传送

    由于蓝牙是无线空中传输,所以数据很容易被窃听,于是蓝牙在数据发送和接收过程中使用了AES加密,关于AES算法超出了本文的范围,大家只需要了解该算法是通过一组密钥(假设密钥是123456)。然后接收方和发送方都使用这个密钥对数据进行加密和解密,传输在空中的数据都是经过加密了的数据,这样安全性就大大提高。AES算法虽然目前没有暴力破解的方法,但是攻击者一旦知道了该密钥就能轻松获取数据,所以密钥的交换一般不通过空中传输,所以蓝牙设计者设计了两个解决方案:OOB和MITM,MITM其实就是在一端生成密钥并显示出来,另一端手动的输入密钥,这样完成密钥交换。OOB和MITM类似,但是密钥的交换通过NFC或串口等形式进行交换。这样攻击者就很难获取密钥进行破解了。当密钥完成交换之后会有Bonding过程,主要目的是保存密钥,下次连接的时候就不需要输入密钥从而直接连接。

    所以在程序中使用GAPBondMgr实现,过程如下:

    • 配对过程通过选择配对模式中描述的以下方法交换密钥。
    • 使用步骤1的密钥加密链接。
    • 绑定过程将密钥存储在安全闪存(SNV)中。
    • 重新连接时,使用存储在SNV中的密钥来加密链接。

    执行所有这些步骤是不必要的。例如,两个设备可以选择配对而不绑定(Bond)。

    选择配对模式

    有四种类型的配对模型,每种模型都在GAPBondMgr中详细描述,用于不同配对模式:

    • Just Work(安全连接或LE遗留)
    • PassKey Entry(安全连接或LE遗留)
    • Numeric Comparison(安全连接)
    • OOB (Out of Band)(安全连接或LE遗留)

    其中Just Work 不能防止攻击者攻击,用于配对没有显示没有输入的设备,主动发起连接即可配对,用户看不到配对过程,例如连接蓝牙耳机。

    PassKey Enter 配对目标输入一个在本地设备上显示的6位数字,输入正确即可配对,并可以防止攻击。使用场景:一端设备可以显示,另一端设备可以输入。

    Numeric Comparison 配对双方都显示一个6位的数字,由用户来核对数字是否一致,并输入Yes/No,两端Yes表示一致即可配对,可以防止中间人攻击。使用场景:两端设备可以弹出6位十进制数,并且有yes和no按钮。

    Out of Band 一般很少使用,通过串口或NFC交换密钥,例如NFC蓝牙音箱。

    所以开发者需要根据自己设备IO输入输出能力选择合适的配对模式。下面给出一些配置参考。

    如果两个设备都支持安全连接,使用下图决定下一步配置。

    如果至少有一个设备不支持安全连接,使用下图决定下一步。

    下表根据IO Capabilities再对模式进行细分

    使用GAPBondMgr

    本节介绍应用程序必须做什么来配置,启动和使用GAPBondMgr。GAPRole处理一些GAPBondMgr功能。GAPBondMgr被定义在gapbondmgr.c 和gapbondmgr.h。BLE Stack API参考(GAPBondMgr部分)描述了完整的API,包括命令,可配置参数,事件和回调。

    使用GAPBondMgr模块的一般步骤如下:

    1. 配置堆栈以包括GAPBondMgr功能,如果需要安全连接。在堆栈项目build_config.opt中定义以下内容:-DGAP_BOND_MGR

    2. 堆栈还必须配置为使用1或2个SNV页面,通过在堆栈项目中定义OSAL_SNV=1或OSAL_SNV=2作为预处理器定义的符号。

    3. 如果使用安全连接,则PDU大小必须大于等于69.这可以通过在应用程序项目中定义以下预处理器符号来设置MAX_PDU_SIZE=69。此外, 可用于安全连接的最小堆大小为3690。

    4. 通过根据需要初始化其参数来配置GAPBondMgr。有关具有描述功能的参数的完整列表,请参阅BLE Stack API参考(GAPBondMgr部分)。有关不同配对模式的GAPBondMgr示例中的各种配对/绑定模式的示例。

    5. 使用GAPBondMgr注册应用程序回调,以便应用程序可以与GAPBondMgr通信并通知事件。

       // Register with bond manager after starting device
       GAPBondMgr_Register(&bondmanager_callbacks);
      

    一旦GAPBondMgr被配置,它主要从应用程序的角度自主运行。当建立连接时,根据初始化期间设置的配置参数启动配对和绑定,并根据需要通过定义的回调与应用程序进行通信。

    可以随时从应用程序中设置一些参数和异步调用的函数。有关详细信息,请参阅BLE Stack API参考(GAPBondMgr部分)。

    GAPBondMgr与此应用程序之间的大多数通信都是通过在步骤5中注册的回调发生的。 下图是GAPBondMgr的流程图示例,通知应用程序配对已经完成。对于各种其他事件的发生也是用相同的方法,并将在后面部分进行扩展。

    GAPBondMgr不同配对模式的示例

    本节提供了上节提到的几种配对模式在应用程序中怎么通过修改参数进行配置,本节提供了程序消息框图,这些模式假设安全模式具有可以接受的IO输入/输出能力。这些例子只考虑配对方面,关于bond实现请参阅下一节,这里的代码片段不是完整的功能示例,仅用于说明目的。

    禁用配对

    配对设置FALSE后,BLE堆栈会自动拒绝任何配对尝试。如下配置GAPBondMgr以禁用配对:

    uint8  pairMode  =  GAPBOND_PAIRING_MODE_NO_PAIRING ; 
    GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &pairMode );
    

    Just work 配对

    Just Works配对允许加密而不需要MITM身份验证,并且容易受到攻击。Just Works配对可以是LE Legacy或Secure Connections配对。GAPBondMgr不需要任何额外的输入从应用程序对于Just Work配对。配置GAPBondMgr为Just Works配对如下。

    	uint8_t  pairMode  =  GAPBOND_PAIRING_MODE_INITIATE ; 
    	uint8_t  mitm  =  FALSE ; 
    	GAPBondMgr_SetParameter ( GAPBOND_PAIRING_MODE , sizeof  (uint8_t ), &pairMode ); 
    	GAPBondMgr_SetParameter ( GAPBOND_MITM_PROTECTION , sizeof  (uint8_t ), &mitm );
    

    下图描述了GAPBondMgr和Just Works配对应用程序之间的交互。如图所示,应用程序GAPBOND_PAIRING_STATE_STARTED一旦发送配对请求就收到一个事件,GAPBOND_PAIRING_STATE_COMPLETE一旦配对过程完成,就会发生一个事件。此时,链接被加密。

    PassKey Entry

    密钥条目是一种可以防止MITM攻击的身份验证配对。它可以是LE Legacy配对或安全连接配对。在这种配对方法中,一个设备显示6位密码,另一个设备输入密码。如选择配对模式所述,IO功能决定哪个设备执行哪个角色。GAPBondMgr在启动时注册的密码回调用于输入或显示密码。以下是启动Passcode Entry配对的示例,其中显示密码。

    1. 定义密码回调

       // Bond ManagerCB
       static  gapBondCBs_t  security_examples_central_bondCB  = 
       { 
         (pfnPasscodeCB_t )security_examples_central_passcodeCB , //密码回调
         security_examples_central_pairStateCB                   //配对状态回调
       };
       
       static  void  security_examples_central_passcodeCB (uint8_t  * deviceAddr , uint16_t  connHandle , uint8_t  uiInputs , uint8_t  uiOutputs , uint32_t  numComparison )
       { 
         gapPasskeyNeededEvent_t  * pData ;  //为密码事件分配空间
       
         if  ((pData  =  ICall_malloc (sizeof (gapPasskeyNeededEvent_t ))))
         { 
           memcpy (pData - > deviceAddr , deviceAddr , B_ADDR_LEN ); 
           pData - > connectionHandle  =  connHandle ; 
           pData - > uiInputs  =  uiInputs ; 
           pData - > uiOutputs  =  uiOutputs ;
       
           //排队事件 
           security_examples_central_enqueueMsg (SEC_PASSCODE_NEEDED_EVT , 0 , (uint8_t  * ) pData ); 
         } 
       }
      
    2. 配置GAPBondMgr

       uint8_t  pairMode  =  GAPBOND_PAIRING_MODE_INITIATE ; 
       uint8_t  mitm  =  TRUE ; 
       GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &uint8_t  pairMode  =  GAPBOND_PAIRING_MODE_INITIATE ; 
       GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &mitm );
      
    3. 处理密码回调并向协议栈发送响应

       static  void  security_examples_central_processPasscode (uint16_t  connectionHandle , gapPasskeyNeededEvent_t  * pData )
       {
       
         if  (pData - > uiInputs ) //如果我们要输入passkey 
         { 
           passcode  =  111111 ; 
           //发送密码响应
           GAPBondMgr_PasscodeRsp (connectionHandle , SUCCESS , passcode ); 
         }
       
         else  if  (pData - > uiOutputs ) //如果我们要显示密码
         { 
           passcode  =  111111 ; 
           DISPLAY_WRITE_STRING_VALUE (“passcode:%d” , passcode, LCD_PAGE4 );
       
           //发送密码响应
           GAPBondMgr_PasscodeRsp (connectionHandle , SUCCESS , passcode ); 
         } 
       }
      

    根据uiInputs和uiOutputs从GAPBondMgr返回的内容,必须显示或输入密码。然后使用GAPBondMgr_PasscodeRsp()将密码发送到GAPBondMgr ,以便配对可以继续。在这种情况下,密码静态设置为111111.在实际产品中,密码很可能是随机生成的,设备必须为用户提供输入密码的方式,然后使用GAPBondMgr_PasscodeRsp()发送给GAPBondMgr 。GAPBondMgr和应用程序之间的一个示例交互如图所示。

    Numeric Comparison

    数字比较是一种经过身份验证的配对,可以防止MITM攻击。只能作为安全连接配对; 不是LE遗产。对于数字比较配对,两个设备都显示6位数的代码。每个设备必须通过按钮或其他一些无输入来指示代码是否匹配。GAPBondMgr在启动时注册的密码回调用于显示6位数代码。以下是启动显示密码的数字比较配对的示例。必须适当设置IO功能才能选择数值比较(即Yes-No)。

    1. 定义密码回调来显示代码。

       // Bond Manager回调
       static  gapBondCBs_t  SimpleBLECentral_bondCB  = 
       { 
         (pfnPasscodeCB_t )SimpleBLECentral_passcodeCB , //密码回调
         SimpleBLECentral_pairStateCB                   //配对状态回调
       }; 
       
       static  void  SimpleBLECentral_passcodeCB  (uint8_t  * deviceAddr , uint16_t  connHandle , uint8_t  uiInputs , uint8_t  uiOutputs , uint32_t  numComparison )
       { 
         gapPasskeyNeededEvent_t  * pData ; 
       
         //为密码事件分配空间 
         if  ((pData  =  ICall_malloc (sizeof (gapPasskeyNeededEvent_t ))))
         { 
           memcpy (pData - > deviceAddr , deviceAddr , B_ADDR_LEN ); 
           pData - > connectionHandle  =  connHandle ; 
           pData - > numComparison  =  numComparison ; 
       
           //排队事件 
           security_examples_central_enqueueMsg (SEC_PASSCODE_NEEDED_EVT , 0 ,   
      
    2. 配置GAPBondMgr

       uint8_t  pairMode  =  GAPBOND_PAIRING_MODE_INITIATE ; 
       uint8_t  scMode  =  GAPBOND_SECURE_CONNECTION_ONLY ; 
       uint8_t  mitm  =  TRUE ; 
       uint8_t  ioCap  =  GAPBOND_IO_CAP_DISPLAY_YES_NO ; 
       
       GAPBondMgr_SetParameter (GAPBOND_IO_CAPABILITIES , sizeof (uint8_t ), &ioCap ); 
       GAPBondMgr_SetParameter (GAPBOND_PAIRING_MODE , sizeof (uint8_t ), &pairMode ); 
       GAPBondMgr_SetParameter (GAPBOND_MITM_PROTECTION , sizeof (uint8_t ), &mitm ); 
       GAPBondMgr_SetParameter (GAPBOND_SECURE_CONNECTION , sizeof (uint8_t ), &scMode );
      
    3. 处理密码回调和显示代码

       static  void  SimpleBLECentral_processPasscode  (uint16_t  connectionHandle , gapPasskeyNeededEvent_t  * pData )
       { 
       
         if  (pData - > numComparison ) //数值比较
         { 
       
           //显示密码
           DISPLAY_WRITE_STRING_VALUE (“Num Cmp:%d” , pData - > numComparison , LCD_PAGE4 ); 
         } 
       }
      
    4. 接受用户输入Yes-No,并发送回应GAPBondMgr。

       if  (keys  & KEY_RIGHT )
       { 
         GAPBondMgr_PasscodeRsp (connHandle , SUCCESS , TRUE ); 
         DISPLAY_WRITE_STRING (“Codes Match!” , LCD_PAGE5 ); 
         return; 
       }
      

    在这种情况下,通常接受密码的GAPBondMgr_PasscodeRsp的第三个参数被重载,以向堆栈发送TRUE,以指示代码匹配并继续配对。数字比较的过程如图56所示。

    Bonding示例

    可以通过GAPBOND_BONDING_ENABLED参数对任何类型的配对启用或禁用绑定,并在配对过程完成后进行绑定 。要启用绑定,请按如下所示配置GAPBondMgr:

    uint8_t  bonding  =  TRUE ; 
    GAPBondMgr_SetParameter (GAPBOND_BONDING_ENABLED , sizeof (uint8_t ), &bonding );
    

    启用绑定后,GAPBondMgr将配对过程中传输的长期密钥存储到SNV。有关详细信息,请参阅GAPBondMgr和SNV。完成后,通过GAPBOND_PAIRING_STATE_COMPLETE事件GAPBOND_PAIRING_STATE_BOND_SAVED通知应用程序。最初连接,配对和绑定时只传递给应用程序对状态回调。为了将来连接到绑定设备,安全密钥从闪存加载,从而跳过配对过程。在这种情况下,只 GAPBOND_PAIRING_STATE_BONDED传递给应用程序对状态回调。这在图57中示出。

    GAPBondMgr和SNV

    本节介绍GAPBondMgr如何使用SNV闪存区域来存储bond信息。有关SNV本身的更多信息,请参阅内存管理 Flash部分。可以存储的bond数量由GAP_BONDINGS_MAX定义设置,默认情况下在gapbondmgr.h中设置为10。GAPBondMgr的功能在不存在可用的绑定时会有所不同。有关参数的更多信息,请参阅BLE Stack API参考(GAPBondMgr部分)。GAPBOND_LRU_BOND_REPLACEMENT:如果此参数设置为false,则不删除bond从而不能添加任何更多的bond。如果参数设置为true,则删除最近最少使用的bond为新bond腾出空间。

    1. bond记录:这个结构体包括设备的地址,地址类型,隐私重新连接地址和状态标志

      typedef struct
      {
      uint8 publicAddr[B_ADDR_LEN]; // Peer's address
      uint8 publicAddrType; // Peer's address type
      uint8 reconnectAddr[B_ADDR_LEN]; // Privacy Reconnection Address
      uint8 stateFlags; // State flags: @ref GAP_BONDED_STATE_FLAGS
      } gapBondRec_t;

    2. 客户端特征配置 Client Characteristic Configurations(CCC):存储在每个条目中的CCC数量由GAP_CHAR_CFG_MAXdefine 设置。默认设置为4。每个CCC由4个字节组成,定义如下:

      typedef struct
      {
      uint16 attrHandle ; //属性句柄
      uint8 value ; //该设备的属性值
      } gapBondCharCfg_t ;

    3. 本地长期密钥(LTK)信息:这存储本地设备的加密信息。这包括28个字节,其组成如下:

      typedef struct
      {
      uint8 LTK [ KEYLEN ]; //长期密钥(LTK)
      uint16 div ; // lint -e754 // LTK eDiv
      uint8 rand [ B_RANDOM_NUM_SIZE ]; // LTK随机数
      uint8 keySize ; // LTK key size
      } gapBondLTK_t ;

    4. 连接设备长期密钥信息:这将存储连接的设备的加密信息。这也是一个gapBondLTK_t,包含28个字节。

    5. 连接的设备身份解析密钥(IRK):存储配对期间生成的IRK。这是一个16字节的数组。

    6. 连接的设备符号解析密钥(SRK):这将存储配对期间生成的SRK。这是一个16字节的数组。

    7. 连接设备标志计数器:这将存储配对期间生成的符号计数器。这是一个4字节的字。

    加入我们

    文章所有代码、工具、文档开源。加入我们QQ群 591679055获取更多支持,共同研究CC2640R2F&BLE5.0。

    CC2640R2F&BLE5.0-乐控畅联© Copyright 2017, 成都乐控畅联科技有限公司.

    展开全文
  • 蓝牙

    2019-05-27 17:02:50
    一:蓝牙开发,需要添加蓝牙权限 <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 二:分类:经典蓝牙、低...

    一:蓝牙开发,需要添加蓝牙权限

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

    二:分类:经典蓝牙、低功耗蓝牙、双向蓝牙

    三:经典蓝牙:

    搜索设备:

    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
    registerReceiver(mBroadcastReceiver, filter);

    广播接收处理搜索到的设备

     
        private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                switch (action) {
                    case BluetoothDevice.ACTION_ACL_DISCONNECTED://断开连接
                        Log.e(TAG, "断开连接设备");
                        break;
                    case BluetoothDevice.ACTION_FOUND:
                        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                        Log.e(TAG, "搜索到的蓝牙设备");
                        break;
                    case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
                        device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                        Log.e(TAG, "设备状态的改变");
                        break;
                }
            }
        };

    开启线程,连接设备

    Method m = currentDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
    socket = (BluetoothSocket) m.invoke(currentDevice, 1);
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Log.e(TAG, "开始连接");
                socket.connect();
                isAccept = true;
                Log.d(TAG, "连接成功,开始处理接收到的数据================");
                InputStream is = socket.getInputStream();
                byte[] buffer;
                while (isAccept) {
                int length = is.available();        //获取buffer里的数据长度
                while (length != 0) {
                    buffer = new byte[length];    //初始化byte数组为buffer中数据的长度
                    is.read(buffer);
                    length = is.available();
                    Log.e(TAG, "设备传信息:" + Arrays.toString(buffer));
                }
            } catch (Exception e) {
                Log.e(TAG, "连接失败信息: " + e.toString());
            }
        }
    }).start();
    展开全文
  • Android 蓝牙开发(一)蓝牙通信

    万次阅读 多人点赞 2016-06-07 15:57:55
    随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。 总结了下蓝牙开发使用的一些东西分享一下。 首先需要AndroidManifest.xml文件中添加操作蓝牙的权限。 /> 允许程序连接到已配对的蓝牙设备。 ...

    1 蓝牙基本操作

    随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。

    总结了下蓝牙开发使用的一些东西分享一下。

    蓝牙权限

    首先需要AndroidManifest.xml文件中添加操作蓝牙的权限。

    <uses-permissionandroid:name="android.permission.BLUETOOTH" />

    允许程序连接到已配对的蓝牙设备。

    <uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />

    允许程序发现和配对蓝牙设备。


    BluetoothAdapter

    操作蓝牙主要用到的类 BluetoothAdapter类,使用时导包
    import android.bluetooth.BluetoothAdapter;
    源码具体位置frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java

    BluetoothAdapter 代表本地设备的蓝牙适配器。该BluetoothAdapter可以执行基本的蓝牙任务,例如启
    动设备发现,查询配对的设备列表,使用已知的MAC地址实例化一个BluetoothDevice类,并创建一个
    BluetoothServerSocket监听来自其他设备的连接请求。

    获取蓝牙适配器

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    开启蓝牙

     if(!mBluetoothAdapter.isEnabled()){
    //弹出对话框提示用户是后打开
    Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enabler, REQUEST_ENABLE);
          //不做提示,直接打开,不建议用下面的方法,有的手机会有问题。
          // mBluetoothAdapter.enable();
    }

    获取本地蓝牙信息

    //获取本机蓝牙名称
    String name = mBluetoothAdapter.getName();
    //获取本机蓝牙地址
    String address = mBluetoothAdapter.getAddress();
    Log.d(TAG,"bluetooth name ="+name+" address ="+address);
    //获取已配对蓝牙设备
    Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
    Log.d(TAG, "bonded device size ="+devices.size());
    for(BluetoothDevice bonddevice:devices){
    	Log.d(TAG, "bonded device name ="+bonddevice.getName()+" address"+bonddevice.getAddress());
    }

    搜索设备

    mBluetoothAdapter.startDiscovery();

    停止搜索

    mBluetoothAdapter.cancelDiscovery();

    搜索蓝牙设备,该过程是异步的,通过下面注册广播接受者,可以监听是否搜到设备。

    IntentFilter filter = new IntentFilter();
    //发现设备
    filter.addAction(BluetoothDevice.ACTION_FOUND);
    //设备连接状态改变
    filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    //蓝牙设备状态改变
    filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
    registerReceiver(mBluetoothReceiver, filter);

    监听扫描结果

    通过广播接收者查看扫描到的蓝牙设备,每扫描到一个设备,系统都会发送此广播(BluetoothDevice.ACTION_FOUNDE)。其中参数intent可以获取蓝牙设备BluetoothDevice

    该demo中是连接指定名称的蓝牙设备,BLUETOOTH_NAME为"Galaxy Nexus",如果扫描不到,记得改这个蓝牙名称。

    private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver(){
    		@Override
    		public void onReceive(Context context, Intent intent) {
    			String action = intent.getAction();
    			Log.d(TAG,"mBluetoothReceiver action ="+action);
    			if(BluetoothDevice.ACTION_FOUND.equals(action)){//每扫描到一个设备,系统都会发送此广播。
    				//获取蓝牙设备
    				BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    				if(scanDevice == null || scanDevice.getName() == null) return;
    				Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress());
    				//蓝牙设备名称
    				String name = scanDevice.getName();
    				if(name != null && name.equals(BLUETOOTH_NAME)){
    					mBluetoothAdapter.cancelDiscovery();
    					//取消扫描
    					mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting));					//连接到设备。
    					mBlthChatUtil.connect(scanDevice);
    				}
    			}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
    
    			}
    		}
    		
    	};

    设置蓝牙可见性

    有时候扫描不到某设备,这是因为该设备对外不可见或者距离远,需要设备该蓝牙可见,这样该才能被搜索到。

    可见时间默认值为120s,最多可设置300。

    if (mBluetoothAdapter.isEnabled()) {
    	if (mBluetoothAdapter.getScanMode() != 
    			BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
    		Intent discoverableIntent = new Intent(
    				BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    		discoverableIntent.putExtra(
    				BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
    		startActivity(discoverableIntent);
    	}
    }

    2 服务端

    android 蓝牙之间可以通过SDP协议建立连接进行通信,通信方式类似于平常使用socket。

    首先创建BluetoothServerSocket ,BluetoothAdapter中提供了两种创建BluetoothServerSocket 方式,如下图所示为创建安全的RFCOMM Bluetooth socket,该连接是安全的需要进行配对。而通过listenUsingInsecureRfcommWithServiceRecord创建的RFCOMM Bluetooth socket是不安全的,连接时不需要进行配对。

    其中的uuid需要服务器端和客户端进行统一。

    private class AcceptThread extends Thread {
            // 本地服务器套接字
            private final BluetoothServerSocket mServerSocket;
            public AcceptThread() {       	
                BluetoothServerSocket tmp = null;
                // 创建一个新的侦听服务器套接字
                try {
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(
                    		SERVICE_NAME, SERVICE_UUID);
                	//tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID);
                } catch (IOException e) {
                    Log.e(TAG, "listen() failed", e);
                }
                mServerSocket = tmp;
            }
    
            public void run() {
                BluetoothSocket socket = null;
                // 循环,直到连接成功
                while (mState != STATE_CONNECTED) {
                    try {
                        // 这是一个阻塞调用 返回成功的连接
                        // mServerSocket.close()在另一个线程中调用,可以中止该阻塞
                        socket = mServerSocket.accept();
                    } catch (IOException e) {
                        Log.e(TAG, "accept() failed", e);
                        break;
                    }
                    // 如果连接被接受
                    if (socket != null) {
                        synchronized (BluetoothChatUtil.this) {
                            switch (mState) {
                            case STATE_LISTEN:
                            case STATE_CONNECTING:
                                // 正常情况。启动ConnectedThread。
                                connected(socket, socket.getRemoteDevice());
                                break;
                            case STATE_NONE:
                            case STATE_CONNECTED:
                                // 没有准备或已连接。新连接终止。
                                try {
                                    socket.close();
                                } catch (IOException e) {
                                    Log.e(TAG, "Could not close unwanted socket", e);
                                }
                                break;
                            }
                        }
                    }
                }
                if (D) Log.i(TAG, "END mAcceptThread");
            }
    
            public void cancel() {
                if (D) Log.d(TAG, "cancel " + this);
                try {
                    mServerSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of server failed", e);
                }
            }
        }
    mServerSocket通过accept()等待客户端的连接(阻塞),直到连接成功或失败。


    3 客户端

    客户端主要用来创建RFCOMM socket,并连接服务端。

    先扫描周围的蓝牙设备,如果扫描到指定设备则进行连接。mBlthChatUtil.connect(scanDevice)连接到设备,

    连接过程主要在ConnectThread线程中进行,先创建socket,方式有两种,

    如下代码中是安全的(createRfcommSocketToServiceRecord)。另一种不安全连接对应的函数是createInsecureRfcommSocketToServiceRecord

    private class ConnectThread extends Thread {
            private BluetoothSocket mmSocket;
            private final BluetoothDevice mmDevice;
            public ConnectThread(BluetoothDevice device) {
                mmDevice = device;
                BluetoothSocket tmp = null;
                // 得到一个bluetoothsocket
                try {
                	mmSocket = device.createRfcommSocketToServiceRecord
                			(SERVICE_UUID);
                } catch (IOException e) {
                    Log.e(TAG, "create() failed", e);
                    mmSocket = null;
                }
            }
    
            public void run() {
                Log.i(TAG, "BEGIN mConnectThread");
                try { 
                    // socket 连接,该调用会阻塞,直到连接成功或失败
                    mmSocket.connect();
                } catch (IOException e) {
                    connectionFailed();
                    try {//关闭这个socket
                        mmSocket.close();
                    } catch (IOException e2) {
                        e2.printStackTrace();
                    }
                    return;
                }
                // 启动连接线程
                connected(mmSocket, mmDevice);
            }
    
            public void cancel() {
                try {
                    mmSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of connect socket failed", e);
                }
            }
        }
    接着客户端socket主动连接服务端。连接过程中会自动进行配对,需要双方同意才可以连接成功。


    4 数据传输

    客户端与服务端连接成功后都会调用connected(mmSocket, mmDevice),创建一个ConnectedThread线程()。

    该线程主要用来接收和发送数据。客户端和服务端处理方式一样。该线程通过socket获得输入输出流。

    private  InputStream mmInStream = socket.getInputStream();

    private  OutputStream mmOutStream =socket.getOutputStream();

    发送数据

    public void write(byte[] buffer) {
        try {
            mmOutStream.write(buffer);
            // 分享发送的信息到Activity
            mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)
                    .sendToTarget();
        } catch (IOException e) {
            Log.e(TAG, "Exception during write", e);
        }
    }
    接收数据

    线程循环进行接收数据。

    public void run() {
        // 监听输入流
        while (true) {
            try {
                byte[] buffer = new byte[1024];
                // 读取输入流
                int bytes = mmInStream.read(buffer);
                // 发送获得的字节的ui activity
                Message msg = mHandler.obtainMessage(MESSAGE_READ);
                Bundle bundle = new Bundle();
                bundle.putByteArray(READ_MSG, buffer);
                msg.setData(bundle);
                mHandler.sendMessage(msg);          
            } catch (IOException e) {
                Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
       }

    demo下载:http://www.demodashi.com/demo/10676.html

    欢迎扫一扫关注我的微信公众号,定期推送优质技术文章


    展开全文
  • SYD8801是一款低功耗高性能蓝牙低功耗SOC,集成了高性能2.4GHz射频收发机、32位ARM Cortex-M0处理器、128kB Flash存储器、以及丰富的数字接口。SYD8801片上集成了Balun无需阻抗匹配网络、高效率DCDC降压转换器,适合...
  • 一,前期基础知识储备 1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI); 支持移动电话、笔记本电脑、无线耳机等设备之间进行信息的交换;...经典蓝牙,3.0版本以下的蓝牙,功耗高,传输
  • NRF52832 SDK15.3 ble bond

    2019-11-16 07:38:04
    NRF52832 SDK15.3蓝牙绑定例程,在官网uart例程基础上演示,共三个例程,无密码绑定,静态密码绑定,动态密码绑定,支持断电保存最近一台绑定的设备信息
  • Android 蓝牙开发(三)蓝牙Hid 开发

    万次阅读 热门讨论 2017-03-13 15:34:40
    Demo下载:... 最近客户需求,android系统的一个设备,想连接一个扫描枪(类似蓝牙键盘,只支持hid协议),并且可以收到其数据,填写到输入框中。我这里借了一个蓝牙鼠标,用来与android设备连接。 ...
  • 简单的蓝牙功能打开蓝牙 关闭蓝牙 搜索蓝牙 清单文件需要加权限 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission....
  • bluetooth-bond

    2019-10-23 17:31:50
  • 蓝牙通信之蓝牙连接

    2021-08-18 19:11:39
    串接上文,蓝牙扫描到设备之后存储到RecyclerView列表中,设置点击回调,点击之后开始连接蓝牙 class BlueToothScanAdapter( private val context: Context, private val deviceList: ArrayList<...
  • Android蓝牙开发—经典蓝牙详细开发流程

    万次阅读 多人点赞 2018-07-16 13:41:12
    Android蓝牙开发前,首先要区分是经典蓝牙开发还是BLE(低功耗)蓝牙开发,它们的开发是有区别的,如果还分不清经典蓝牙和BLE(低功耗)蓝牙的小伙伴,可以先看Android蓝牙开发—经典蓝牙和BLE(低功耗)蓝牙的区别 ...
  • 经典蓝牙连接-蓝牙音箱或蓝牙耳机

    千次阅读 2016-07-11 10:13:41
    生活中我們经常使用蓝牙耳机来接听电话,而隨著科技的進步,現在蓝牙多了一种新的规范:A2DP(Advance Audio Distribution Profile),可以用 44.1 kHz 的 來传输声音,因此現在可以使用蓝牙耳机來享受音樂,接下來就...
  • Android蓝牙开发(一)之打开蓝牙和设备搜索

    万次阅读 多人点赞 2018-08-29 17:10:04
    Android蓝牙开发系列目录: https://blog.csdn.net/huangliniqng/article/details/82185635 一、判断是否系统是否支持蓝牙 在使用蓝牙之前,我们首先要判断手机设备是否支持蓝牙,虽然现在基本都支持蓝牙了,但是...
  • 最近项目需要做蓝牙连接蓝牙秤设备,获取称重数据,然后就去研究了下蓝牙相关知识,看过一堆一堆的博客文章,然后开始写,依然踏了很多的坑,自己各种办法试了又试,终于完善得差不多了,然后现在项目做完了有空就写...
  • 蓝牙配对

    2015-11-23 17:37:27
    蓝牙

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,338
精华内容 935
关键字:

蓝牙bond