蓝牙inquiry流程之Advertising Report
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; #endifcase 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) { //判断子eventcase 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,但是通常只有一个eventwhile (num_reports--) {/* Extract inquiry results */STREAM_TO_UINT8(legacy_evt_type, p);//event_typeSTREAM_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++;}elsereturn;}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;}elsep_i->scan_rsp = TRUE;//拿到scan responseif (p_i->inq_count != p_inq->inq_counter)p_cur->device_type = BT_DEVICE_TYPE_BLE;//这次inquiry的第一次处理elsep_cur->device_type |= BT_DEVICE_TYPE_BLE;//if(p_i->inq_count == p_inq->inq_counter) indicated that has been updated ,至少是BLEif (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)//读取flagp_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);//填充addressmemcpy(result.inq_res.dev_class, p_inq->dev_class, DEV_CLASS_LEN);//填充device classBTM_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 callbackcopy 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);//codif (!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);//保存codnum_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; #elsedev_type = BT_DEVICE_TYPE_BREDR; #endifBTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type), &dev_type);//保存dev typenum_properties++;/* RSSI */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_RSSI, sizeof(int8_t),&(p_search_data->inq_res.rssi));//保存rssinum_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 设备广播包的处理就分析到这里。
转载于:https://www.cnblogs.com/libs-liu/p/9238209.html
蓝牙inquiry流程之Advertising Report相关推荐
- 蓝牙inquiry流程之Inquiry Complete处理
inquiry流程一般持续有12s多,当inquiry完成的时候,设备端会上报一个Event: Inquiry Complete 上来,那协议栈是如何把这个事件上传到应用层的呢?本篇文章来分析一下其具 ...
- 数字IC后端设计实现流程之initial design
数字IC后端设计实现流程之initial design 前端提供的文件 门级网表 (Gate Level Netlist) 数字前端工程师或者是 R2N 的同事在 release 东西给后端工程师做 ...
- LiteOS学习笔记-9LiteOS SDK oc流程之MQTT
LiteOS学习笔记-9LiteOS SDK oc流程之MQTT MQTT协议 MQTT协议简介 MQTT消息类型 LiteOS中MQTT实现 LiteOS OC MQTT 抽象组件 概述 配置并连接 ...
- 源码详解Android 9.0(P) 系统启动流程之SystemServer
源码详解Android 9.0(P) 系统启动流程目录: 源码详解Android 9.0(P)系统启动流程之init进程(第一阶段) 源码详解Android 9.0(P)系统启动流程之init进程(第 ...
- activiti自己定义流程之Spring整合activiti-modeler5.16实例(四):部署流程定义
注:(1)环境搭建:activiti自己定义流程之Spring整合activiti-modeler5.16实例(一):环境搭建 (2)创建流程模型:activiti自己定义流程之Spr ...
- activiti自定义流程之Spring整合activiti-modeler5.16实例(四):部署流程定义
注:(1)环境搭建:activiti自定义流程之Spring整合activiti-modeler5.16实例(一):环境搭建 (2)创建流程模型:activiti自定义流程之Sprin ...
- [原创]FOCUS处理系统流程之:流程批量生成(个人专用懒人版)
根据标准流程生成整个工区流程文件 (1)可按按目录生成 (2)可替换其中的关键字 标准流程准确,则生成的流程将一点到底,所剩的操作变成鼠标的操作,懒之产物. 界面如下: ☆其它物探处理原创软件相关☆ ...
- [原创]FOCUS处理系统流程之:大文本文件极速合并(sps文件合并)
下载地址:csdn 软件界面1: (功能如题) : 1.普通文本文件的合并 2.大文件快速合并,空行及重复行删除等功能 3.合并物探 ...
- 数字IC后端设计实现流程之floorplan及powerplan规划
数字IC后端设计实现floorplan及powerplan规划 数字 IC 后端设计实现流程之 initial design 初始化 Initial 模块形状 估算完模块的面积后,block owne ...
最新文章
- mysql 函数返回表格_mysql 数据分析如何实现日报、周报、月报和年报?
- vue组件级路由钩子函数介绍,及实际应用
- oracle触发器修改同一张表,oracle触发器中对同一张表进行更新再查询时,需加自制事务...
- 【数据结构与算法】之深入解析“罗马数字转整数”的求解思路与算法示例
- 【简便解法】1074 宇宙无敌加法器 (20分)_40行代码AC
- Elasticsearch整理笔记(五)
- php中国天气api接口,免费天气预报API接口使用教程(信息来源权威及时)
- lua怎么嵌入php,linux下安装php的lua扩展
- 计算机网络性能(1)
- 机器学习基础:Dice Loss(Machine Learning Fundamentals: Dice Loss)
- 《BackTrack 5 Cookbook中文版——渗透测试实用技巧荟萃》—第3章3.4节识别在线设备...
- kernel中的memtest
- Android studio - UI 界面设计(仿问卷星登陆注册界面)
- IBM深陷云计算业务欺诈丑闻,遭股东集体诉讼
- 复指数与高斯函数乘积的傅里叶变换_量子力学杂谈——格林函数
- 【MySQL 09】安装mysql时出现:需要这两个包perl(Data::Dumper),perl(JSON)
- 关于 iOS 的 StoryBoard,接受的那一刻才发现她的美 - 当然美的事物都需要业心照料
- ubuntu20 关闭防火墙_ubuntu中如何关闭防火墙
- 2019年安徽省大数据与人工智能应用赛总结---本科组
- 工欲善其事必先利其器–SimpleTestBed