iOS 蓝牙开发

Bluetooth.png

蓝牙介绍

本文要介绍的CoreBluetooth,专门用于与BLE设备通讯。并且现在很多蓝牙设备都支持4.0,4.0以其低功耗著称,所以一般也叫BLE(Bluetooth Low Energy),所以也是在iOS比较推荐的一种开发方法。

基本概念

image.png
  • 中心设备(Central):发起连接,用来扫描周围蓝牙硬件的设备;
  • 周边设备(Peripheral):被连接的设备;
  • 服务(Service):特征和关系的集合,它封装了设备的一部分的行为;
  • 特征(Characteristic):可以理解成一个Service模块具体提供哪些服务,特征会有一个value,一般我们向蓝牙设备写入数据、从蓝牙设备读取数据就是这个value;
  • UUID:区分不同服务和特征的唯一标识,使用该字端我们可以获取我们想要的服务或者特征。
  • 广播:外部设备不停的散播的蓝牙信号,让中心设备可以扫描到,也是我们开发中接收数据的入口。

CoreBluetooth介绍

image.png

在CoreBluetooth中有两个主要的部分,Central和Peripheral,CBPeripheralManager 作为周边设备。CBCentralManager作为中心设备。所有可用的iOS设备可以作为周边设备(Peripheral)也可以作为中心设备(Central),但不可以同时既是周边设备也是中心设备。

Central

作为 Central 模式使用的操作步骤 :

  1. 创建一个 Central Manager;
  2. 发现周边设备;
  3. 发现周边设备的服务及特征;
  4. 读取特征值或对指定特征设置监听;
  5. 设置可写入的特征值;
  6. 读取特征的描述Descriptor;

创建了一个 tableView 来扫描到的周边设备的名字、信号强度等;

导入CoreBluetooth

#import <CoreBluetooth/CoreBluetooth.h>

创建一个管理者centralManager,给当前类签订协议<cbcentralmanagerdelegate>:</cbcentralmanagerdelegate>

_centralManager=[[CBCentralManager alloc] initWithDelegate:self queue:nil];

实现 centralManagerDidUpdateState: 监测蓝牙状态变化:

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {

    switch (central.state) {
        case CBCentralManagerStatePoweredOff:
        {
            NSLog(@"蓝牙功能未开启");
        }
            break;
        case CBCentralManagerStateUnsupported:
        {
            NSLog(@"不支持蓝牙功能");
        }
            break;
        case CBCentralManagerStatePoweredOn:
        {
            NSLog(@"蓝牙功能已开启");
        }
            break;
        default:
            break;
    }
}

扫描周边设备

[_centralManager scanForPeripheralsWithServices:nil options:nil];

实现协议方法获取扫描到的周边设备

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
    __block BOOL isContain = NO;
    SABLEInfoObject *bleInfo = [[SABLEInfoObject alloc] init];
    bleInfo.name = peripheral.name ?: @"--";
    bleInfo.rssi = RSSI;
    bleInfo.deviceUUID = peripheral.identifier.UUIDString;
    bleInfo.servicesCount = peripheral.services.count;
    [self.dataArray enumerateObjectsUsingBlock:^(SABLEInfoObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj.deviceUUID isEqualToString:bleInfo.deviceUUID]) {
            [self.dataArray replaceObjectAtIndex:idx withObject:bleInfo];
            isContain = YES;
        }
    }];

    if (!isContain) {
        [self.dataArray addObject:bleInfo];
        [self.peripheralList addObject:peripheral];
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:kDiscoverPeripheralRefreshNotificationName object:self.dataArray];
}

在创建的 tableView 中实时显示 peripheral 的信号强度、名称、服务个数,选取某一个发起连接:

[_centralManager connectPeripheral:self.peripheralList[index] options:nil];

实现协议方法 centralManager:didConnectPeripheral 在连接成功时去获取当前设备包含的所有服务:

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"%@ Connect Success", peripheral.name);
    peripheral.delegate = self;
    [peripheral discoverServices:nil];
}

协议方法 didDiscoverServices,遍历服务,查询服务下的特征:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error {
    NSLog(@"%@ Discover Services Success", peripheral.name);
    [peripheral.services enumerateObjectsUsingBlock:^(CBService * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"Service UUID: %@", obj.UUID.UUIDString);
        [peripheral discoverCharacteristics:nil forService:obj];
    }];
}

协议方法 didDiscoverCharacteristicsForService,遍历当前服务下的特征,读取特征值或者监测特征值,还可获取特征的Descriptor:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    [service.characteristics enumerateObjectsUsingBlock:^(CBCharacteristic * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"\nservicesUUID: %@\nCharacteristicUUID: %@", service.UUID.UUIDString, obj);
        //读取特征
//        if ([obj.UUID.UUIDString isEqualToString:@"2A19"]) {
//            [peripheral readValueForCharacteristic:obj];
//        }
//
//        if ([service.UUID.UUIDString isEqualToString:@"180A"]) {
//            [peripheral readValueForCharacteristic:obj];
//        }
        [peripheral readValueForCharacteristic:obj];
        if (obj.properties == CBCharacteristicPropertyWrite) {
            NSLog(@"writeValue");
            [peripheral writeValue:[@"testValue " dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:obj type:CBCharacteristicWriteWithResponse];
        }

        //监测特征
//        [peripheral setNotifyValue:YES forCharacteristic:obj];

        // 外设发现特征的描述
        [peripheral discoverDescriptorsForCharacteristic:obj]; 
    }];
}

读取或者监测的特征值可在协议方法 didUpdateValueForCharacteristic 查看,如下,读取电量值,2A19为电量值特征的UUID:

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
    if ([characteristic.UUID.UUIDString isEqualToString:@"2A19"]) {
        NSString *hexStr = [SADataConvertManager convertDataToHexStr:characteristic.value];
        NSUInteger batteryLevel = [[SADataConvertManager convertHexStrToDecimalString:hexStr] integerValue];
        NSLog(@"\nCharacteristicUUID: %@\nCharacteristicValue: %@", characteristic.UUID, [@(batteryLevel).stringValue stringByAppendingString:@"%"]);
    }
}

发现的 Descriptor 需要实现协议方法读取:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    [characteristic.descriptors enumerateObjectsUsingBlock:^(CBDescriptor * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"\nCharacteristicUUID: %@\nDescriptor: %@", characteristic.UUID, obj.value);
    }];
}

周边设备读取信号强度:

[peripheral readRSSI];  //读取信号强度

实现协议方法 didReadRSSI 获取读取的信号强度:

- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error {
    NSLog(@"信号强度:%@", RSSI.stringValue);
}

写入特征值:

if (obj.properties == CBCharacteristicPropertyWrite) {
            [peripheral writeValue:[@"testValue " dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:obj type:CBCharacteristicWriteWithResponse];
        }

Peripheral

作为 Peripheral 模式使用的操作步骤 :

  1. 创建一个 Peripheral Manager;
  2. 为外设添加服务;
  3. 发送广播

创建一个管理者peripheralManager,给当前类签订协议<cbperipheralmanagerdelegate>:</cbperipheralmanagerdelegate>

_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];

实现 centralManagerDidUpdateState: 获取蓝牙状态变化:

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    switch (peripheral.state) {
        case CBPeripheralManagerStatePoweredOn:
        {
            NSLog(@"蓝牙已打开");
            [self setupPeripheral];
        }
            break;
        case CBPeripheralManagerStatePoweredOff:
            NSLog(@"蓝牙已关闭");
            break;
        case CBPeripheralManagerStateUnsupported:
            NSLog(@"不支持蓝牙");
            break;

        default:
            break;
    }
}

为蓝牙添加服务

- (void)setupPeripheral {
    CBMutableCharacteristic *myCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"] properties:CBCharacteristicPropertyRead value:[@"123456789" dataUsingEncoding:NSUTF8StringEncoding] permissions:CBAttributePermissionsReadable];
    CBMutableService *myService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"12AB"] primary:YES];
    myService.characteristics = @[myCharacteristic];
    [_peripheralManager addService:myService];
}

添加服务成功执行代理方法 didAddService :

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
    [service.characteristics enumerateObjectsUsingBlock:^(CBCharacteristic * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"\nserviceUUID: %@\nCharacteristicUUID: %@\nCharacteristicValue: %@", service.UUID.UUIDString, obj.UUID.UUIDString, [[NSString alloc] initWithData:obj.value encoding:NSUTF8StringEncoding]);
    }];
}

打印结果:
serviceUUID: 12AB
CharacteristicUUID: 71DA3FD1-7E10-41C1-B16F-4430B506CDE7
CharacteristicValue: 123456789

发送广播:

- (void)setupPeripheralAdvertising {
    if (!_peripheralManager.isAdvertising) {
        CBMutableCharacteristic *myCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"] properties:CBCharacteristicPropertyRead value:[@"123456789" dataUsingEncoding:NSUTF8StringEncoding] permissions:CBAttributePermissionsReadable];
        CBMutableService *myService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"12AB"] primary:YES];
        myService.characteristics = @[myCharacteristic];
        [_peripheralManager addService:myService];
        [_peripheralManager startAdvertising:@{
                                               CBAdvertisementDataLocalNameKey : @"测试设备",
                                               CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:@"12AB"]]
                                               }];
        [SAAudioSound sa_playPassSound];
    }else {
        [_peripheralManager stopAdvertising];
        [SAAudioSound sa_playWarningSound];
    }
}

广播发送开启成功后会走协议方法:peripheralManagerDidStartAdvertising

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(nullable NSError *)error {
    NSLog(@"正在广播");
}

当连接到中心设备时便可接收读或写的请求,接收到请求时会走相应的协议方法:

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {

}

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {

}

附:

GATT服务UUID: https://www.bluetooth.com/specifications/gatt/services

GATT特征UUID: https://www.bluetooth.com/specifications/gatt/characteristics

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容