零基础iOS开发学习日记-功能篇-Bluetooth蓝牙

1,798 阅读7分钟

开头

Bluetooth蓝牙

实际用处

  • 与蓝牙设备进行通信
  • 物联网设备的开发
  • 最近正好在整蓝牙的项目,就顺便把iOS自带的框架学了下;项目中的框架用的是BabyBluetooth

关键词铺垫

  • 中心设备,就是用来扫描周围蓝牙硬件的设备,比如手机的蓝牙来扫描并连接智能手环,这时候手机就是中心设备
  • 外设,被扫描的设备,例如上面的智能手环
  • 广播,外设不停地向外面发送蓝牙信号,让中心设备可以扫描到
  • 服务,services,外设广播和运行的时候会有服务,可以理解成一个功能模块,中心设备可以读取服务。外设可以有多个服务
  • 特征,在服务中的一个单位,一个服务可以有多个特征,特征会有一个value,一般读写的数据就是这个value
  • UUID,区分不同的服务和特征,可以理解为服务和特征的身份证,用UUID来挑选需要的服务和特征。
  • 订阅,类似监听

info.plist配置

<key>NSBluetoothAlwaysUsageDescription</key>
<string></string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>YES</string>

外设信息

  • 这部分写在前面,因为代码部分比较多
  • 外设
<CBPeripheral: 0x281d34280, //对象值
identifier = E6F8E0AB-6B1E-200F-056C-05750B1A0D7A, 
name = CH9140BLE2U, 
state = connecting>
  • 广播数据
kCBAdvDataIsConnectable = 1;
kCBAdvDataLocalName = CH9140BLE2U; //外设名
kCBAdvDataManufacturerData = {length = 8, bytes = 0x0cc6209000000000}; //MAC值
kCBAdvDataTimestamp = "644931976.188664";
kCBAdvDataTxPowerLevel = 0;
  • RSSI 信号强度,连接质量
-56
  • service
<CBService: 0x2838409c0, isPrimary = YES, UUID = FFF0>
  • Characteristic
//第一个特征值
<CBCharacteristic: 0x28118c240, UUID = FFF1, properties = 0x12, value = (null), notifying = NO>
//第二个特征值
<CBCharacteristic: 0x28118c2a0, UUID = FFF2, properties = 0xC, value = (null), notifying = NO>
//第三个特征值
<CBCharacteristic: 0x28118c300, UUID = FFF3, properties = 0x1E, value = (null), notifying = NO>

配置中心设备

  • 目的,让设备又能去扫描外面的蓝牙设备
  • 对于中心设备来说,关键任务就是扫描蓝牙连接蓝牙获取蓝牙的写、监听端口,而这个端口指的就是特征值,一般需要嵌入式去规定
  • 而下面整套流程走下来,就是这个顺序,扫描蓝牙->连接蓝牙->扫描蓝牙的服务->扫描蓝牙的特征值->处理特征值的需求->对蓝牙订阅或者是写操作,外加一些错误处理、数据处理,及蓝牙信息筛选等过滤操作,就可以连接蓝牙了
  • 整体操作思路比较简单,顺一遍就有感觉了
  • 头文件
#import <CoreBluetooth/CoreBluetooth.h>

初始

#define SERVICE_UUID @"FFF0"
#define CHARACTERISTIC_NOTIFY_UUID @"FFF1" //蓝牙向中心设备发
#define CHARACTERISTIC_WRITE_UUID @"FFF2" //中心设备向蓝牙发
#define CHARACTERISTIC_READ_UUID @"FFF3" //中心设备读蓝牙准备好的数据
@interface ViewController () <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, strong) CBPeripheral *peripheral;
//写端口
@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;
//读端口
@property (nonatomic, strong) CBCharacteristic *notifyCharacteristic;
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化后,自动扫描
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
    UIButton *postBtn = [UIButton buttonWithType:UIButtonTypeContactAdd];
    postBtn.frame = CGRectMake(100, 100, 100, 100);
    [postBtn addTarget:self action:@selector(didClickPost) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:postBtn];
    //主动读
    UIButton *readBtn = [UIButton buttonWithType:UIButtonTypeContactAdd];
    readBtn.frame = CGRectMake(200, 200, 100, 100);
    readBtn.tintColor = [UIColor orangeColor];
    [readBtn addTarget:self action:@selector(didClickGet) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:readBtn];
}

扫描状态

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    //蓝牙可用,开始扫描外设
    if (central.state == CBManagerStatePoweredOn) {
        NSLog(@"蓝牙可用 - %@", central);
        //扫描ID,无差别扫描
        [central scanForPeripheralsWithServices:@[] options:nil];
        //针对性扫描
//        [central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:SERVICE_UUID]] options:nil];
    }
}

发现外设

-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    //对外设对象进行强引用
    self.peripheral = peripheral;
    //过滤设备
//    if ([peripheral.name hasPrefix:@"XX"]) {
//        [central connectPeripheral:peripheral options:nil];
//    }
    //连接自己写的外设
//    if([peripheral.name isEqualToString:@"iPhone"]) {
//        [central connectPeripheral:peripheral options:nil];
//        NSLog(@"%@", peripheral);
//        NSLog(@"%@", peripheral.name);
//        NSLog(@"%@", advertisementData);
//        NSLog(@"%@", RSSI);
//    }
    //获取MAC值,并判断是不是所需要的
    NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
    if([data isEqualToData:[self convertHexStrToData:@"0cc6209000000000"]]) {
        //这里匹配后是自动连接,可以写成tableView的点击方法
        [central connectPeripheral:peripheral options:nil];
        NSLog(@"%@", peripheral);
        NSLog(@"%@", advertisementData);
        NSLog(@"%@", RSSI);
    }
    
}

连接成功

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    [central stopScan];
    //设置代理
    peripheral.delegate = self;
    //无差别寻找
    [peripheral discoverServices:@[]];
    //根据UUID寻找服务
//    [peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];

    NSLog(@"connecct success");
}

连接失败的回调

-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"连接失败");
}

断开连接

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
    NSLog(@"断开连接");
    // 断开连接可以设置重新连接
    [central connectPeripheral:peripheral options:nil];
}

发现服务

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    for (CBService *service in peripheral.services) {
        NSLog(@"service : %@", service);
    }
    CBService *service = peripheral.services.lastObject;
    //寻找特征 无差别寻找
    [peripheral discoverCharacteristics:@[] forService:service];
    //寻找指定的特征 [CBUUID UUIDWithString:CHARACTERISTIC_NOFITY_UUID]
//    [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
}

发现特征

  • 取出对应的特征值
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    
    // 遍历出所需要的特征
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"所有特征:%@", characteristic);
        // 从外设开发人员那里拿到不同特征的UUID,不同特征做不同事情,比如有读取数据的特征,也有写入数据的特征
        if ([characteristic.UUID.UUIDString isEqualToString:CHARACTERISTIC_WRITE_UUID]) {
            self.writeCharacteristic = characteristic;
        }
        else if([characteristic.UUID.UUIDString isEqualToString:CHARACTERISTIC_NOTIFY_UUID]) {
            self.notifyCharacteristic = characteristic;
            // 订阅通知 - 监听这个端口
            [peripheral setNotifyValue:YES forCharacteristic:self.notifyCharacteristic];
        }
        else if([characteristic.UUID.UUIDString isEqualToString:CHARACTERISTIC_READ_UUID]) {
            self.readCharacteristic = characteristic;
        }
        
    }
}

订阅状态的改变

  • 是否监听成功
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"订阅失败");
        NSLog(@"%@",error);
    }
    if (characteristic.isNotifying) {
        NSLog(@"订阅成功");
    } else {
        NSLog(@"取消订阅");
    }
}

接收到数据回调

  • 对于收到的数据的处理
  • 读、监听,都经过这个回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    // 拿到外设发送过来的数据
    NSData *data = characteristic.value;
    NSLog(@"%@", data);
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}

写入数据回调

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
    NSLog(@"写入成功");
}

向外设发送信息

- (void)didClickPost {
    NSString *str = @"i am centralManager";
    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    [self.peripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];
    
}

主动向外设读取数据

- (void)didClickGet {
    [self.peripheral readValueForCharacteristic:self.readCharacteristic];
}

16进制转NSData

  • 处理获取的设备MAC值
- (NSData *)convertHexStrToData:(NSString *)str
{
    if (!str || [str length] == 0) {
        return nil;
    }
    
    NSMutableData *hexData = [[NSMutableData alloc] initWithCapacity:20];
    NSRange range;
    if ([str length] % 2 == 0) {
        range = NSMakeRange(0, 2);
    } else {
        range = NSMakeRange(0, 1);
    }
    for (NSInteger i = range.location; i < [str length]; i += 2) {
        unsigned int anInt;
        NSString *hexCharStr = [str substringWithRange:range];
        NSScanner *scanner = [[NSScanner alloc] initWithString:hexCharStr];
        
        [scanner scanHexInt:&anInt];
        NSData *entity = [[NSData alloc] initWithBytes:&anInt length:1];
        [hexData appendData:entity];
        
        range.location += range.length;
        range.length = 2;
    }
    return hexData;
}

配置蓝牙设备

  • 目的,让中心设备能扫描到蓝牙设备
  • 思路围绕着一个蓝牙设备需要什么,需要服务特征值广播信息等,然后确定端口,按照步骤写下来就好
  • 在写的时候,特征值要与中心设备相对应,要站在一个角度去写特征值,比如,中心设备的写也就是蓝牙中的被写

初始配置

#define SERVICE_UUID @"FFF0"
#define CHARACTERISTIC_NOTIFY_UUID @"FFF1"
#define CHARACTERISTIC_WRITE_UUID @"FFF2"
#define CHARACTERISTIC_READ_UUID @"FFF3"

@interface ViewController () <CBPeripheralManagerDelegate>
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
//蓝牙给中心设备发数据
@property (nonatomic, strong) CBMutableCharacteristic *notifyCharacteristic;
//中心设备给蓝牙发数据
@property (nonatomic, strong) CBMutableCharacteristic *writeCharacteristic;
//蓝牙准备好数据让中心设备读
@property (nonatomic, strong) CBMutableCharacteristic *readCharacteristic;
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
    UIButton *postBtn = [UIButton buttonWithType:UIButtonTypeContactAdd];
    postBtn.frame = CGRectMake(100, 100, 100, 100);
    [postBtn addTarget:self action:@selector(didClickPost) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:postBtn];
}

更新蓝牙状态

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state == CBManagerStatePoweredOn) {
        //创建 服务 和 特征
        [self setupServiceAndCharacteristics];
        //根据服务的UUID开始广播
//        [self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:SERVICE_UUID]]}];
    }
}

设置服务和特征值

- (void)setupServiceAndCharacteristics {
    //创建服务
    CBUUID *serviceID = [CBUUID UUIDWithString:SERVICE_UUID];
    CBMutableService *service = [[CBMutableService alloc] initWithType:serviceID primary:YES];
    //创建服务中的特征
    CBUUID *characteristicNotifyID = [CBUUID UUIDWithString:CHARACTERISTIC_NOTIFY_UUID];
    CBUUID *characteristicWriteID = [CBUUID UUIDWithString:CHARACTERISTIC_WRITE_UUID];
    CBUUID *characteristicReadID = [CBUUID UUIDWithString:CHARACTERISTIC_READ_UUID];
    
    //properties 服务中包括的功能,读、写、监听
    CBMutableCharacteristic *notifyCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicNotifyID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
    CBMutableCharacteristic *writeCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicWriteID properties:CBCharacteristicPropertyWrite value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
    
    CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicReadID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
    //把特征值添加到服务
    service.characteristics = @[notifyCharacteristic, writeCharacteristic, readCharacteristic];
    //把服务加入管理
    [self.peripheralManager addService:service];
    //手动给中心设备发送数据
    self.notifyCharacteristic = notifyCharacteristic;
    self.writeCharacteristic = writeCharacteristic;
    self.readCharacteristic = readCharacteristic;
}

添加服务后的回调函数

  • 进行广播,只能设置服务的UUID和广播名
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
    [self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:SERVICE_UUID]],CBAdvertisementDataLocalNameKey:@"宝找到你了"}];
}

发送广播的回调函数

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
    NSLog(@"device is  in advertising.");
}

外设被读取的时候的回调

  • 当中心设备向蓝牙读数据时,这里会被事先准备好的数据发给中心设备
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
    //请求中的数据,把内容发送给中心设备
    NSString *str = @"i am periheral";
    request.value = [str dataUsingEncoding:NSUTF8StringEncoding];
    //成功响应
    [peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}

写入数据的回调

  • 指的是,中心设备通过写特征值向蓝牙发送数据
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
    //写入数据的请求
    CBATTRequest *request = requests.lastObject;
    //输出写入数据
    NSLog(@"%@", [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding]);
}

主动给中心设备发送数据

  • 用notif的特征值进行发送
- (void)didClickPost {
    NSString *str = @"i am notifyCharacteristic";
    bool sendSuccess = [self.peripheralManager updateValue:[str dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.notifyCharacteristic onSubscribedCentrals:nil];
    if (sendSuccess) {
        NSLog(@"数据发送成功");
    }else {
        NSLog(@"数据发送失败");
    }
}

订阅成功的回调 - 订阅类比监听

-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"%s",__FUNCTION__);
}

取消订阅的回调

-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"%s",__FUNCTION__);
}

总结

  • 这篇比较偏使用攻略,后面有时间,会把里面的关键函数的参数含义整理一下