setting 界面開始搜索的時候,通常也會同時進行le scan,這一點在inquiry流程之命令下發中已經講述。此篇文章主要是分析一下對於controller 搜索到的廣播包的處理。這里以Android O的bluedroid的代碼作為分析對象。
void btu_hci_msg_process(BT_HDR* p_msg) { /* Determine the input message type. */ switch (p_msg->event & BT_EVT_MASK) { case BT_EVT_TO_BTU_HCI_ACL: /* All Acl Data goes to L2CAP */ l2c_rcv_acl_data(p_msg); break; case BT_EVT_TO_BTU_L2C_SEG_XMIT: /* L2CAP segment transmit complete */ l2c_link_segments_xmitted(p_msg); break; case BT_EVT_TO_BTU_HCI_SCO: #if (BTM_SCO_INCLUDED == TRUE) btm_route_sco_data(p_msg); break; #endif case BT_EVT_TO_BTU_HCI_EVT: btu_hcif_process_event((uint8_t)(p_msg->event & BT_SUB_EVT_MASK), p_msg); osi_free(p_msg); break; case BT_EVT_TO_BTU_HCI_CMD: btu_hcif_send_cmd((uint8_t)(p_msg->event & BT_SUB_EVT_MASK), p_msg); break; default: osi_free(p_msg); break; } }
上面可以看出 btu_hci_msg_process 的所有的處理對象。hci event 的處理函數 是btu_hcif_process_event((uint8_t)(p_msg->event & BT_SUB_EVT_MASK), p_msg);
void btu_hcif_process_event(UNUSED_ATTR uint8_t controller_id, BT_HDR* p_msg) { uint8_t* p = (uint8_t*)(p_msg + 1) + p_msg->offset; uint8_t hci_evt_code, hci_evt_len; uint8_t ble_sub_code; STREAM_TO_UINT8(hci_evt_code, p); STREAM_TO_UINT8(hci_evt_len, p); switch (hci_evt_code) { case HCI_INQUIRY_COMP_EVT: btu_hcif_inquiry_comp_evt(p); break; case HCI_INQUIRY_RESULT_EVT: btu_hcif_inquiry_result_evt(p); break; case HCI_INQUIRY_RSSI_RESULT_EVT: btu_hcif_inquiry_rssi_result_evt(p); break; ... case HCI_BLE_EVENT: { //le 相關的event STREAM_TO_UINT8(ble_sub_code, p); uint8_t ble_evt_len = hci_evt_len - 1; switch (ble_sub_code) { //判斷子event case HCI_BLE_ADV_PKT_RPT_EVT: /* result of inquiry */ HCI_TRACE_EVENT("HCI_BLE_ADV_PKT_RPT_EVT"); btm_ble_process_adv_pkt(ble_evt_len, p);//處理廣播包 break; case HCI_BLE_CONN_COMPLETE_EVT: btu_ble_ll_conn_complete_evt(p, hci_evt_len); break; case HCI_BLE_LL_CONN_PARAM_UPD_EVT: btu_ble_ll_conn_param_upd_evt(p, hci_evt_len); break; ...
從上面可以看出 btu_hcif_process_event可以處理的event 的類型,並且可以看出處理廣播包的函數 是 btm_ble_process_adv_pkt(ble_evt_len, p);從函數名稱可以看出此時函數已經進入到btm 模塊,其實現在btm_ble_gap.cc
/** * This function is called when advertising report event is received. It updates * the inquiry database. If the inquiry database is full, the oldest entry is * discarded. */ void btm_ble_process_adv_pkt(uint8_t data_len, uint8_t* data) { BD_ADDR bda; uint8_t* p = data; uint8_t legacy_evt_type, addr_type, num_reports, pkt_data_len; int8_t rssi; /* Extract the number of reports in this event. */ STREAM_TO_UINT8(num_reports, p);//一個包里面可能有多個event,但是通常只有一個event while (num_reports--) { /* Extract inquiry results */ STREAM_TO_UINT8(legacy_evt_type, p);//event_type STREAM_TO_UINT8(addr_type, p);//地址類型 STREAM_TO_BDADDR(bda, p);//地址 STREAM_TO_UINT8(pkt_data_len, p);//數據長度 uint8_t* pkt_data = p; p += pkt_data_len; /* Advance to the the rssi byte */ STREAM_TO_INT8(rssi, p);//此時指針指向數據末尾的rssi btm_ble_process_adv_addr(bda, addr_type);//處理地址相關 uint16_t event_type; if (legacy_evt_type == 0x00) { // ADV_IND; event_type = 0x0013; } else if (legacy_evt_type == 0x01) { // ADV_DIRECT_IND; event_type = 0x0015; } else if (legacy_evt_type == 0x02) { // ADV_SCAN_IND; event_type = 0x0012; } else if (legacy_evt_type == 0x03) { // ADV_NONCONN_IND; event_type = 0x0010; } else if (legacy_evt_type == 0x04) { // SCAN_RSP; // We can't distinguish between "SCAN_RSP to an ADV_IND", and "SCAN_RSP to // an ADV_SCAN_IND", so always return "SCAN_RSP to an ADV_IND" event_type = 0x001B; } btm_ble_process_adv_pkt_cont( event_type, addr_type, bda, PHY_LE_1M, PHY_LE_NO_PACKET, NO_ADI_PRESENT, TX_POWER_NOT_PRESENT, rssi, 0x00 /* no periodic adv */, pkt_data_len, pkt_data);//開始處理數據包 } }
可以看出主要的 主要的處理流程是執行btm_ble_process_adv_pkt_cont,我們看看具體的實現:
/******************************************************************************* ** ** Function btm_ble_process_adv_pkt_cont ** ** Description This function is called after random address resolution is ** done, and proceed to process adv packet. ** ** Parameters ** ** Returns void ** *******************************************************************************/ static void btm_ble_process_adv_pkt_cont(BD_ADDR bda, UINT8 addr_type, UINT8 evt_type, UINT8 *p) { tINQ_DB_ENT *p_i; tBTM_INQUIRY_VAR_ST *p_inq = &btm_cb.btm_inq_vars; tBTM_INQ_RESULTS_CB *p_inq_results_cb = p_inq->p_inq_results_cb; tBTM_INQ_RESULTS_CB *p_obs_results_cb = btm_cb.ble_ctr_cb.p_obs_results_cb; tBTM_BLE_INQ_CB *p_le_inq_cb = &btm_cb.ble_ctr_cb.inq_var; BOOLEAN update = TRUE; UINT8 result = 0; p_i = btm_inq_db_find (bda); /* Check if this address has already been processed for this inquiry */ if (btm_inq_find_bdaddr(bda))//check 這個地址的設備是否已經被處理過 { /* never been report as an LE device */ if (p_i && (!(p_i->inq_info.results.device_type & BT_DEVICE_TYPE_BLE) || /* scan repsonse to be updated */ (!p_i->scan_rsp)))//這里update的判斷條件是device type是否已經定位以及是否有scan response { update = TRUE; } else if (BTM_BLE_IS_OBS_ACTIVE(btm_cb.ble_ctr_cb.scan_activity)) { update = FALSE; } else { /* if yes, skip it */ return; /* assumption: one result per event */ } } /* If existing entry, use that, else get a new one (possibly reusing the oldest) */ if (p_i == NULL)//沒有entry,重新分配 { if ((p_i = btm_inq_db_new (bda)) != NULL) { p_inq->inq_cmpl_info.num_resp++; } else return; } else if (p_i->inq_count != p_inq->inq_counter) /* first time seen in this inquiry */ /*在這一次的inquiry 中第一次見到該設備*/ { p_inq->inq_cmpl_info.num_resp++; } /* update the LE device information in inquiry database */ if (!btm_ble_update_inq_result(p_i, addr_type, evt_type, p))//更新數據庫 return; if ((result = btm_ble_is_discoverable(bda, evt_type, p)) == 0)//檢測adv包中標志位 { LOG_WARN("%s device is no longer discoverable so discarding advertising packet pkt", __func__); return; } if (!update) result &= ~BTM_BLE_INQ_RESULT; ... /* background connection in selective connection mode */ BTM_TRACE_WARNING("btm_cb.ble_ctr_cb.bg_conn_type = %d libs_liu",btm_cb.ble_ctr_cb.bg_conn_type); if (btm_cb.ble_ctr_cb.bg_conn_type == BTM_BLE_CONN_SELECTIVE) { if (result & BTM_BLE_SEL_CONN_RESULT) btm_send_sel_conn_callback(bda, evt_type, p, addr_type); else { BTM_TRACE_DEBUG("None LE device, can not initiate selective connection"); } } else { if (p_inq_results_cb && (result & BTM_BLE_INQ_RESULT)) { (p_inq_results_cb)((tBTM_INQ_RESULTS *) &p_i->inq_info.results, p_le_inq_cb->adv_data_cache);//處理結果 } if (p_obs_results_cb && (result & BTM_BLE_OBS_RESULT)) { (p_obs_results_cb)((tBTM_INQ_RESULTS *) &p_i->inq_info.results, p_le_inq_cb->adv_data_cache); } } }
從上面處理流程來看,主要是做了四件事:
- 查詢設備數據庫,並且判斷該廣播信息已經被處理過。
- btm_ble_update_inq_result(p_i, addr_type, evt_type, p) 更新設備數據庫中的設備信息
- btm_ble_is_discoverable(bda, evt_type, p) 判斷設備信息標志位,判斷是否是可以發現的類型。
- 調用p_inq_results_cb 來處理 設備信息。這個在BTM_StartInquiry的時候傳入參數bta_dm_inq_results_cb,調用的也就是這個回調函數。
下面主要分析一下2,3,4三個點:
2.btm_ble_update_inq_result(p_i, addr_type, evt_type, p)
/******************************************************************************* ** ** Function btm_ble_update_inq_result ** ** Description Update adv packet information into inquiry result. ** ** Parameters ** ** Returns void ** *******************************************************************************/ BOOLEAN btm_ble_update_inq_result(tINQ_DB_ENT *p_i, UINT8 addr_type, UINT8 evt_type, UINT8 *p) { BOOLEAN to_report = TRUE; tBTM_INQ_RESULTS *p_cur = &p_i->inq_info.results; UINT8 len; UINT8 *p_flag; tBTM_INQUIRY_VAR_ST *p_inq = &btm_cb.btm_inq_vars; UINT8 data_len, rssi; tBTM_BLE_INQ_CB *p_le_inq_cb = &btm_cb.ble_ctr_cb.inq_var; UINT8 *p1; UINT8 *p_uuid16; STREAM_TO_UINT8 (data_len, p); btm_ble_cache_adv_data(p_cur, data_len, p, evt_type);// cache adv data p1 = (p + data_len); STREAM_TO_UINT8 (rssi, p1); /* Save the info */ p_cur->inq_result_type = BTM_INQ_RESULT_BLE; p_cur->ble_addr_type = addr_type; p_cur->rssi = rssi; /* active scan, always wait until get scan_rsp to report the result */ if ((btm_cb.ble_ctr_cb.inq_var.scan_type == BTM_BLE_SCAN_MODE_ACTI && //如果是active scan,那么要等scan response 上來之后才會一起report,一個單獨的BTM_BLE_CONNECT_EVT或者BTM_BLE_DISCOVER_EVT是不會上報的 (evt_type == BTM_BLE_CONNECT_EVT || evt_type == BTM_BLE_DISCOVER_EVT))) { BTM_TRACE_DEBUG("btm_ble_update_inq_result scan_rsp=false, to_report=false,\ scan_type_active=%d", btm_cb.ble_ctr_cb.inq_var.scan_type); p_i->scan_rsp = FALSE; to_report = FALSE; } else p_i->scan_rsp = TRUE;//拿到scan response if (p_i->inq_count != p_inq->inq_counter) p_cur->device_type = BT_DEVICE_TYPE_BLE;//這次inquiry的第一次處理 else p_cur->device_type |= BT_DEVICE_TYPE_BLE;//if(p_i->inq_count == p_inq->inq_counter) indicated that has been updated ,至少是BLE if (evt_type != BTM_BLE_SCAN_RSP_EVT) p_cur->ble_evt_type = evt_type; p_i->inq_count = p_inq->inq_counter; /* Mark entry for current inquiry */ if (p_le_inq_cb->adv_len != 0) { if ((p_flag = BTM_CheckAdvData(p_le_inq_cb->adv_data_cache, BTM_BLE_AD_TYPE_FLAG, &len)) != NULL)//讀取flag p_cur->flag = * p_flag; } if (p_le_inq_cb->adv_len != 0) { /* Check to see the BLE device has the Appearance UUID in the advertising data. If it does * then try to convert the appearance value to a class of device value Bluedroid can use. * Otherwise fall back to trying to infer if it is a HID device based on the service class. */ p_uuid16 = BTM_CheckAdvData(p_le_inq_cb->adv_data_cache, BTM_BLE_AD_TYPE_APPEARANCE, &len); if (p_uuid16 && len == 2) { btm_ble_appearance_to_cod((UINT16)p_uuid16[0] | (p_uuid16[1] << 8), p_cur->dev_class); } else { if ((p_uuid16 = BTM_CheckAdvData(p_le_inq_cb->adv_data_cache, BTM_BLE_AD_TYPE_16SRV_CMPL, &len)) != NULL) { UINT8 i; for (i = 0; i + 2 <= len; i = i + 2) { /* if this BLE device support HID over LE, set HID Major in class of device */ if ((p_uuid16[i] | (p_uuid16[i+1] << 8)) == UUID_SERVCLASS_LE_HID) { p_cur->dev_class[0] = 0; p_cur->dev_class[1] = BTM_COD_MAJOR_PERIPHERAL; p_cur->dev_class[2] = 0; break; } } } } } /* if BR/EDR not supported is not set, assume is a DUMO device */ if ((p_cur->flag & BTM_BLE_BREDR_NOT_SPT) == 0 && evt_type != BTM_BLE_CONNECT_DIR_EVT) { if (p_cur->ble_addr_type != BLE_ADDR_RANDOM) { BTM_TRACE_DEBUG("BR/EDR NOT support bit not set, treat as DUMO"); p_cur->device_type |= BT_DEVICE_TYPE_DUMO; } else { BTM_TRACE_DEBUG("Random address, treating device as LE only"); } } else { BTM_TRACE_DEBUG("BR/EDR NOT SUPPORT bit set, LE only device"); } return to_report; }
這里主要就是更新設備的信息。讀取adv 包中的 flag等值,判斷這個包是否要上報,以及更新device type。另外會根據BTM_BLE_AD_TYPE_APPEARANCE = 0x19 來判斷device class-->btm_ble_appearance_to_cod
3.btm_ble_is_discoverable(bda, evt_type, p)
/******************************************************************************* ** ** Function btm_ble_is_discoverable ** ** Description check ADV flag to make sure device is discoverable and match ** the search condition ** ** Parameters ** ** Returns void ** *******************************************************************************/ UINT8 btm_ble_is_discoverable(BD_ADDR bda, UINT8 evt_type, UINT8 *p) { UINT8 *p_flag, flag = 0, rt = 0; UINT8 data_len; tBTM_INQ_PARMS *p_cond = &btm_cb.btm_inq_vars.inqparms; tBTM_BLE_INQ_CB *p_le_inq_cb = &btm_cb.ble_ctr_cb.inq_var; /* for observer, always "discoverable */ if (BTM_BLE_IS_OBS_ACTIVE(btm_cb.ble_ctr_cb.scan_activity))//這里注意,如果是observation,那么總是可以發現的 rt |= BTM_BLE_OBS_RESULT; if (BTM_BLE_IS_SEL_CONN_ACTIVE(btm_cb.ble_ctr_cb.scan_activity) && (evt_type == BTM_BLE_CONNECT_EVT || evt_type == BTM_BLE_CONNECT_DIR_EVT)) rt |= BTM_BLE_SEL_CONN_RESULT; if (p_le_inq_cb->adv_len != 0) { if ((p_flag = BTM_CheckAdvData(p_le_inq_cb->adv_data_cache, BTM_BLE_AD_TYPE_FLAG, &data_len)) != NULL)//讀取標志位並且判斷 { flag = * p_flag; if ((btm_cb.btm_inq_vars.inq_active & BTM_BLE_GENERAL_INQUIRY) && (flag & (BTM_BLE_LIMIT_DISC_FLAG|BTM_BLE_GEN_DISC_FLAG)) != 0) { BTM_TRACE_DEBUG("Find Generable Discoverable device"); rt |= BTM_BLE_INQ_RESULT; } else if (btm_cb.btm_inq_vars.inq_active & BTM_BLE_LIMITED_INQUIRY && (flag & BTM_BLE_LIMIT_DISC_FLAG) != 0) { BTM_TRACE_DEBUG("Find limited discoverable device"); rt |= BTM_BLE_INQ_RESULT; } } } return rt; }
上面代碼主要作用就是解析adv包中的標志位,然后做解析。並且決定是否要繼續處理這個包,如果rt = 0 ,那么就不會繼續處理這個包。這里注意如果是observation ,那么這個包總是可以發現的。
4.bta_dm_inq_results_cb
下面來分析這個 最重要的函數:
/******************************************************************************* ** ** Function bta_dm_inq_results_cb ** ** Description Inquiry results callback from BTM ** ** Returns void ** *******************************************************************************/ static void bta_dm_inq_results_cb (tBTM_INQ_RESULTS *p_inq, UINT8 *p_eir) { tBTA_DM_SEARCH result;//使用這個結果上報結果 tBTM_INQ_INFO *p_inq_info; UINT16 service_class; bdcpy(result.inq_res.bd_addr, p_inq->remote_bd_addr);//填充address memcpy(result.inq_res.dev_class, p_inq->dev_class, DEV_CLASS_LEN);//填充device class BTM_COD_SERVICE_CLASS(service_class, p_inq->dev_class); result.inq_res.is_limited = (service_class & BTM_COD_SERVICE_LMTD_DISCOVER)?TRUE:FALSE; result.inq_res.rssi = p_inq->rssi;//rssi #if (BLE_INCLUDED == TRUE) result.inq_res.ble_addr_type = p_inq->ble_addr_type; result.inq_res.inq_result_type = p_inq->inq_result_type; result.inq_res.device_type = p_inq->device_type; result.inq_res.flag = p_inq->flag;//填充flag #endif /* application will parse EIR to find out remote device name */ result.inq_res.p_eir = p_eir; if((p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr)) != NULL) { /* initialize remt_name_not_required to FALSE so that we get the name by default */ result.inq_res.remt_name_not_required = FALSE; } if(bta_dm_search_cb.p_search_cback)//回調 bta_dm_search_cb.p_search_cback(BTA_DM_INQ_RES_EVT, &result); if(p_inq_info) { /* application indicates if it knows the remote name, inside the callback copy that to the inquiry data base*/ if(result.inq_res.remt_name_not_required) p_inq_info->appl_knows_rem_name = TRUE;//如果application已經知道名字 } }
這里的邏輯也比較簡單,構造了一個tBTA_DM_SEARCH來上報事件,上報的事件的函數就是bta_dm_search_cb.p_search_cback = bte_search_devices_evt,我們來看看其具體的實現:
/******************************************************************************* ** ** Function bte_search_devices_evt ** ** Description Switches context from BTE to BTIF for DM search events ** ** Returns void ** *******************************************************************************/ static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data) { UINT16 param_len = 0; if (p_data) param_len += sizeof(tBTA_DM_SEARCH); /* Allocate buffer to hold the pointers (deep copy). The pointers will point to the end of the tBTA_DM_SEARCH */ switch (event) { case BTA_DM_INQ_RES_EVT: { if (p_data->inq_res.p_eir) param_len += HCI_EXT_INQ_RESPONSE_LEN; } break; ... /* if remote name is available in EIR, set teh flag so that stack doesnt trigger RNR */ if (event == BTA_DM_INQ_RES_EVT){ p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);//這里check data 里面是否有名字,如果有的話,設置result 的標志位,協議棧就不會去查詢名字 } btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len, (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL); }
這里主要就是check 名字。然后transfer 到btif 線程去執行,我們繼續看:
/****************************************************************************** ** ** Function btif_dm_search_devices_evt ** ** Description Executes search devices callback events in btif context ** ** Returns void ** ******************************************************************************/ static void btif_dm_search_devices_evt (UINT16 event, char *p_param) { tBTA_DM_SEARCH *p_search_data; BTIF_TRACE_EVENT("%s event=%s", __FUNCTION__, dump_dm_search_event(event)); switch (event) { ... case BTA_DM_INQ_RES_EVT: { /* inquiry result */ UINT32 cod; bt_bdname_t bdname; bt_bdaddr_t bdaddr; UINT8 remote_name_len; tBTA_SERVICE_MASK services = 0; bdstr_t bdstr; p_search_data = (tBTA_DM_SEARCH *)p_param;//獲得數據 bdcpy(bdaddr.address, p_search_data->inq_res.bd_addr);//獲得地址 bdname.name[0] = 0; cod = devclass2uint (p_search_data->inq_res.dev_class);//cod if (!check_eir_remote_name(p_search_data, bdname.name, &remote_name_len)) check_cached_remote_name(p_search_data, bdname.name, &remote_name_len);//如果data里面沒有名字,那么就查找cache里面的名字 /* Check EIR for remote name and services */ if (p_search_data->inq_res.p_eir) { BTA_GetEirService(p_search_data->inq_res.p_eir, &services); BTIF_TRACE_DEBUG("%s()EIR BTA services = %08X", __FUNCTION__, (UINT32)services); /* TODO: Get the service list and check to see which uuids we got and send it back to the client. */ } { bt_property_t properties[5]; bt_device_type_t dev_type; uint32_t num_properties = 0; bt_status_t status; int addr_type = 0; memset(properties, 0, sizeof(properties)); /* BD_ADDR */ BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_BDADDR, sizeof(bdaddr), &bdaddr);//保存地址 num_properties++; /* BD_NAME */ /* Don't send BDNAME if it is empty */ if (bdname.name[0]) { BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_BDNAME, strlen((char *)bdname.name), &bdname);//保存名字 num_properties++; } /* DEV_CLASS */ BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod), &cod);//保存cod num_properties++; /* DEV_TYPE */ #if (defined(BLE_INCLUDED) && (BLE_INCLUDED == TRUE)) /* FixMe: Assumption is that bluetooth.h and BTE enums match */ /* Verify if the device is dual mode in NVRAM */ int stored_device_type = 0; if (btif_get_device_type(bdaddr.address, &stored_device_type) && ((stored_device_type == BT_DEVICE_TYPE_BLE && p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BREDR) || (stored_device_type == BT_DEVICE_TYPE_BREDR && p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE))) { dev_type = BT_DEVICE_TYPE_DUMO; } else { dev_type = p_search_data->inq_res.device_type; } if (p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE) addr_type = p_search_data->inq_res.ble_addr_type; #else dev_type = BT_DEVICE_TYPE_BREDR; #endif BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type), &dev_type);//保存dev type num_properties++; /* RSSI */ BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_REMOTE_RSSI, sizeof(int8_t), &(p_search_data->inq_res.rssi));//保存rssi num_properties++; status = btif_storage_add_remote_device(&bdaddr, num_properties, properties);//將各個屬性保存在文件系統中 #if (defined(BLE_INCLUDED) && (BLE_INCLUDED == TRUE)) status = btif_storage_set_remote_addr_type(&bdaddr, addr_type); ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote addr type (inquiry)", status); #endif /* Callback to notify upper layer of device */ HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);//向上匯報 } } break; ...
這里注意btif_storage_add_remote_device 是將各個屬性保存在系統的配置文件中。然后 通過HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties); 來上面五個屬性:設備地址、設備名字、設備類、設備類型、設備rssi
協議棧對於ble 設備廣播包的處理就分析到這里。