扫描peripheral

在看本文前,请先看“BLE广播中的数据包”,至少要知道什么是定时包、扫描回复包。

广播数据由定时包和扫描回复包组成。绝大多数情部下,等回调到didDiscoverPeripheral,会已收到这个包。但偶尔会出现只收到定时包,至少ios14、ios15、ios16都出现过。

 

一、ios

1.1 开始扫描

-(void)scanPeripherals:(const char*)uuid
{
    // [bleManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
    NSMutableArray* uuidArray = [[NSMutableArray alloc] init];
    if (uuid && uuid[0] != '\0') {
        [uuidArray addObject: [CBUUID UUIDWithString: [[NSString alloc] initWithUTF8String:uuid]]];
    }
    [bleManager scanForPeripheralsWithServices:uuidArray options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES,
         CBCentralManagerOptionShowPowerAlertKey:@YES}];
}
  • CBCentralManagerScanOptionAllowDuplicatesKey。控制扫描到重复设备时,是否还要调用didDiscoverPeripheral。设置为yes表示扫描到重复设备,依旧调用didDiscoverPeripheral。由于有可能出现扫描时只收到定时包,又恰好出现在第一次扫描到这设备时,如果不重复,app此轮扫描会认为这peripheral就只有定时包中那些字段。为得到完整广播包,必须把CBCentralManagerScanOptionAllowDuplicatesKey设为true。
  • CBCentralManagerOptionShowPowerAlertKey。网上有说它设为YES, 是在蓝牙未打开的时候显示弹框。但我测试下来,没用。但个人认希望有这功能,就设置为YES。

 

1.2 扫描到一个peripheral后的回调

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:
        (CBPeripheral *)peripheral2 advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    const char* name = [peripheral2.name UTF8String];
    if (name == NULL || name[0] == '\0') {
        // don't support "empty" name.
        // "empty" name maybe result very much "fake" ble device.
        return;
    }

检查广播数据中是否有name字段,若没有name字段,忽略这个广播。这意味着,这要求peripheral必须广播name字段。   

    bool field_increase = false;
    // field: service_uuid
    NSArray* service_uuids = [advertisementData objectForKey:@"kCBAdvDataServiceUUIDs"];
    int uuid_count = service_uuids != NULL? (int)(long)[service_uuids count]: 0;
    // field: manufacturer_data
    NSData* manufacturer_data = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"];
    
    // NSLog(@"[SDL_uikitble.m]discover peripheral: %@", peripheral2.name);
    SDL_BlePeripheral* peripheral = discover_peripheral_uh_cookie((__bridge void *)(peripheral2), name);
    if (peripheral->cookie != NULL) {
        if (name != NULL) {
            if (peripheral->name == NULL) {
                NSLog(@"[SDL_uikitble.m]%@ it is duplicatesKey, but has name", peripheral2.name);
                field_increase = true;
            } else {
                if (SDL_strcmp(peripheral->name, name) != 0) {
                    field_increase = true;
                }
            }
        }
        
        if (uuid_count != 0 && peripheral->uuid == NULL) {
            NSLog(@"[SDL_uikitble.m]%@ it is duplicatesKey, but has kCBAdvDataServiceUUIDs", peripheral2.name);
            field_increase = true;

对这peripheral,之前扫描到时没有ServiceUUID,此次扫描到时有ServiceUUID。原因应该是这peripherl把ServiceUUID放在了扫描回复包,之前扫描到的没有收到扫描回复包。

        }
        
        if (manufacturer_data != NULL) {
            if (peripheral->manufacturer_data == NULL) {
                NSLog(@"[SDL_uikitble.m]%@ it is duplicatesKey, but has kCBAdvDataManufacturerData", peripheral2.name);
                field_increase = true;
            } else {
                int oc_len = (int)[manufacturer_data length];
                if (oc_len != peripheral->manufacturer_data_len || 
                  SDL_memcmp(peripheral->manufacturer_data, [manufacturer_data bytes], oc_len) != 0) {
                    field_increase = true;
                }
            }
        }
    }

“peripheral->cookie != NULL”,意味着这是个重复设备。这时要检查关注的字段,如果有增加或修改了,还是要告诉app发现了新设备。如果多了字段,原因应该是上次没收到扫描回复包,这次收到了。如查修改了,应该是在两次扫描间隔,peripheral改了相关该字段值,像修改Name、ManufacturerData。通常来说,一旦定了uuid后,就不会去修改它的值,这里针对uuid就只判断是否增加,不判断修改。

关注字段只有三个:Name,ServiceUUID、ManufacturerData。

    if (peripheral->cookie == NULL || field_increase) {
        if (peripheral->cookie != NULL) {
            UIKit_ReleaseCookie(peripheral);
        }
        peripheral->cookie = (void *)CFBridgingRetain(peripheral2);
        // this peripheral must result to call discover_peripheral_bh
        peripheral->discovered = SDL_FALSE;

discovered置为SDL_FALSE,让这peripheral是新设备。discovered作用见底下的discover_peripheral_bh。

        // parse KCBAdvDataServiceUUIDs, result maybe like 1809,ccc0
        size_t require_size = 0;
        for (int at = 0; at < uuid_count; at ++) {
            const char* uuid = [[[service_uuids objectAtIndex: at] UUIDString] UTF8String];
            require_size += SDL_strlen(uuid) + 1; // , or \0'
        }
        if (require_size) {
            peripheral->uuid = (char*)SDL_malloc(require_size);
        }
        int start = 0;
        for (int at = 0; at < uuid_count; at ++) {
            if (at) {
                peripheral->uuid[start] = ',';
                start ++;
            }
            const char* uuid = [[[service_uuids objectAtIndex: at] UUIDString] UTF8String];
            size_t s = SDL_strlen(uuid);
            memcpy(peripheral->uuid + start, uuid, s);
            start += s;
            if (at == uuid_count - 1) {
                peripheral->uuid[start] = '\0';
            }
        }
        
        // kCBAdvDataManufacturerData, only copy.
        if (manufacturer_data) {
            peripheral->manufacturer_data_len = (int)[manufacturer_data length];
            peripheral->manufacturer_data = (unsigned char*)SDL_malloc(peripheral->manufacturer_data_len);
            memcpy(peripheral->manufacturer_data, [manufacturer_data bytes], peripheral->manufacturer_data_len);
    }
    if (!peripheral->discovered) {
        peripheral->discovered = SDL_TRUE;
        // try to reduce times of call call discover_peripheral_bh.
        discover_peripheral_bh(peripheral, [RSSI intValue]);

扫描到设备的频率是不低的,尽可能减少app收到“发现periperhal”。这里用了discovered字段,在开始扫描时,会把所有peripheral->discovered置为SDL_FALSE。只有此轮扫描发现的“新”设备才会发向app。

    }
}

全部评论: 0

    写评论: