iOS 蓝牙开发 - swift版

3,858 阅读9分钟

@[TOC](iOS 蓝牙开发 )

1.蓝牙简介

  • 蓝牙模式简介 蓝牙开发分为两种模式,中心模式(central),和外设模式(peripheral)。一般来讲,我们需要在软件内连接硬件,通过连接硬件给硬件发送指令以完成一些动作的蓝牙开发都是基于中心模式(central)模式的开发,也就是说我们开发的app是中心,我们要连接的硬件是外设。如果需要其他设备连接手机蓝牙,并对手机进行一些操作,那就是基于外设模式(peripheral)的开发。 本次我们主要介绍的就是中心模式的蓝牙开发

  • 设备简介 中心设备(CBCentralManager):iOS系统的手机等设备 外围设备(CBPeripheral):手环等第三方设备

  • 蓝牙数据传输简介 将外围设备(车辆)的数据传送给中心设备(手机)时, 数据是经过两层包装的 第一层是 Service(服务) , 可以是一个或多个, 比如车辆数据(服务) 第二层是 Characteristic(特征) , 他提供了更多关于Service(服务)的数据, 例如车辆数据(服务)中包含了两个数据, 分别是里程数据和续航数据, 这两个就是车辆数据(服务)的具体数据(特征)

  • 具体操作简介 读(read) , 写(write) , 订阅(notify) 我们的目的是读取设备中的数据(read) , 或者给设备写入一定的数据(write)。有时候我们还想设备的数据变化的时候不需要我们手动去读取这个值,需要设备自动通知我们它的值变化了,值是多少。把值告诉app,这个时候就需要订阅这个特征了(notify)

  • 其他相关概念

  1. 目前都使用的是低功耗蓝牙4.0,蓝牙外设必需为4.0及以上(2.0需要MFI认证),否则无法开发,蓝牙4.0设施由于低耗电,所以也叫做BLE。
  2. CoreBluetooth框架的核心其实是两个东西,peripheral和central, 能了解成外设和中心,就是你的苹果手机就是中心,外部蓝牙称为外设。
  3. 服务和特征(service characteristic):简而言之,外部蓝牙中它有若干个服务service(服务你能了解为蓝牙所拥有的可以力),而每个服务service下拥有若干个特征characteristic(特征你能了解为解释这个服务的属性)。
  4. Descriptor(形容)使用来形容characteristic变量的属性。例如,一个descriptor能规定一个可读的形容,或者者一个characteristic变量可接受的范围,或者者一个characteristic变量特定的单位。
  5. 我们用的蓝牙板块是在淘宝买的, 大概十多元一个, ios大概每次能接受90个字节, 安卓大概每次能接收20个字节, 具体数字可可以会浮动, 应该是与蓝牙板块有关。

2. 蓝牙连接

自己实践的两个蓝牙demo:

  1. OC 编写的:Bluetooth4_configWifi
  2. swifit编写的:KYLBluetoothDemo_swift

2.1 CoreBluetooth框架

CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类

在这里插入图片描述

  • 这两组api分别对应不同的业务场景,左侧叫做中心模式,就是以你的app作为中心,连接其他的外设的场景,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景。
  • 服务和特征,特征的属性(service and characteristic): 每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知这么几种方式。
//objcetive c特征的定义枚举
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast                                               = 0x01,
    CBCharacteristicPropertyRead                                                    = 0x02,
    CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
    CBCharacteristicPropertyWrite                                                   = 0x08,
    CBCharacteristicPropertyNotify                                                  = 0x10,
    CBCharacteristicPropertyIndicate                                                = 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites                               = 0x40,
    CBCharacteristicPropertyExtendedProperties                                      = 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)     = 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)   = 0x200
};

2.2 外设、服务、特征间的关系

在这里插入图片描述

在这里插入图片描述

2.3 蓝牙连接过程

  1. 创建中心设备(CBCentralManager)
  2. 中心设备开始扫描(scanForPeripherals)
  3. 扫描到外围设备之后, 自动调用中心设备的代理方法(didDiscoverPeripheral)
  4. 如果设备过多, 可以将扫描到的外围设备添加到数组
  5. 开始连接, 从数组中过滤出自己想要的设备, 进行连接(connectPeripheral)
  6. 连接上之后, 自动调用中心设备的代理方法(didConnectPeripheral), 在代理中, 进行查找外围设备的服务(peripheral.discoverServices)
  7. 查找到服务之后, 自动调用外围设备的代理(didDiscoverServices), 可通过UUID,查找具体的服务,查找服务(discoverCharacteristics)
  8. 查找到特征之后, 自动调用外围设备的代理(didDiscoverCharacteristics), 通过UUID找到自己想要的特征, 读取特征(readValueForCharacteristic)
  9. 读取到特征之后, 自动调用外设的代理方法(didUpdateValueForCharacteristic),在这里打印或者解析自己想要的特征值.

2.4 蓝牙中心模式,外设模式

2.4.1 蓝牙中心模式

  • 中心模式流程
  1. 建立中心角色

  2. 扫描外设(discover)

  3. 连接外设(connect)

  4. 扫描外设中的服务和特征(discover) 4.1 获取外设的services 4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的 Descriptor和Descriptor的值

  5. 与外设做数据交互(explore and interact)

  6. 订阅Characteristic的通知

  7. 断开连接(disconnect)

2.4.2 蓝牙外设模式

  • 蓝牙外设模式流程
  1. 启动一个Peripheral管理对象

  2. 本地Peripheral设置服务,特性,描述,权限等等

  3. Peripheral发送广告

  4. 设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法

2.5 蓝牙设备状态

  • 蓝牙设备状态 待机状态(standby):设备没有传输和发送数据,并且没有连接到任何设 广播状态(Advertiser):周期性广播状态 扫描状态(Scanner):主动寻找正在广播的设备 发起链接状态(Initiator):主动向扫描设备发起连接。 主设备(Master):作为主设备连接到其他设备。 从设备(Slave):作为从设备连接到其他设备。

  • 蓝牙设备的五种工作状态 准备(standby) 广播(advertising) 监听扫描(Scanning 发起连接(Initiating) 已连接(Connected)

2.6 蓝牙连接代码实现

  1. 初始化
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
  1. 搜索扫描外围设备
/**
 *  --  初始化成功自动调用
 *  --  必须实现的代理,用来返回创建的centralManager的状态。
 *  --  注意:必须确认当前是CBCentralManagerStatePoweredOn状态才可以调用扫描外设的方法:
 scanForPeripheralsWithServices
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    switch (central.state) {
        case CBCentralManagerStateUnknown:
            NSLog(@">>>CBCentralManagerStateUnknown");
            break;
        case CBCentralManagerStateResetting:
            NSLog(@">>>CBCentralManagerStateResetting");
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@">>>CBCentralManagerStateUnsupported");
            break;
        case CBCentralManagerStateUnauthorized:
            NSLog(@">>>CBCentralManagerStateUnauthorized");
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@">>>CBCentralManagerStatePoweredOff");
            break;
        case CBCentralManagerStatePoweredOn:
        {
            NSLog(@">>>CBCentralManagerStatePoweredOn");
            // 开始扫描周围的外设。
            /*
             -- 两个参数为Nil表示默认扫描所有可见蓝牙设备。
             -- 注意:第一个参数是用来扫描有指定服务的外设。然后有些外设的服务是相同的,比如都有FFF5服务,那么都会发现;而有些外设的服务是不可见的,就会扫描不到设备。
             -- 成功扫描到外设后调用didDiscoverPeripheral
             */
            [self.centralManager scanForPeripheralsWithServices:nil options:nil];
        }
            break;
        default:
            break;
    }
}

#pragma mark 发现外设
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"Find device:%@", [peripheral name]);
    if (![_deviceDic objectForKey:[peripheral name]]) {
        NSLog(@"Find device:%@", [peripheral name]);
        if (peripheral!=nil) {
            if ([peripheral name]!=nil) {
                if ([[peripheral name] hasPrefix:@"根据设备名过滤"]) {
                    [_deviceDic setObject:peripheral forKey:[peripheral name]];
                     // 停止扫描, 看需求决定要不要加
//                    [_centralManager stopScan];
                    // 将设备信息传到外面的页面(VC), 构成扫描到的设备列表
                    if ([self.delegate respondsToSelector:@selector(dataWithBluetoothDic:)]) {
                        [self.delegate dataWithBluetoothDic:_deviceDic];
                    }
                }
            }
        }
    }
}

  1. 连接外围设备
// 连接设备(.h中声明出去的接口, 一般在点击设备列表连接时调用)
- (void)connectDeviceWithPeripheral:(CBPeripheral *)peripheral
{
    [self.centralManager connectPeripheral:peripheral options:nil];
}



#pragma mark 连接外设--成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //连接成功后停止扫描,节省内存
    [central stopScan];
    peripheral.delegate = self;
    self.peripheral = peripheral;
    //4.扫描外设的服务
    /**
     --     外设的服务、特征、描述等方法是CBPeripheralDelegate的内容,所以要先设置代理peripheral.delegate = self
     --     参数表示你关心的服务的UUID,比如我关心的是"FFE0",参数就可以为@[[CBUUID UUIDWithString:@"FFE0"]].那么didDiscoverServices方法回调内容就只有这两个UUID的服务,不会有其他多余的内容,提高效率。nil表示扫描所有服务
     --     成功发现服务,回调didDiscoverServices
     */
    [peripheral discoverServices:@[[CBUUID UUIDWithString:@"你要用的服务UUID"]]];
    if ([self.delegate respondsToSelector:@selector(didConnectBle)]) {
       // 已经连接
        [self.delegate didConnectBle];
    }
}



#pragma mark 连接外设——失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"%@", error);
}


#pragma mark 取消与外设的连接回调
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"%@", peripheral);
}

  1. 获得外围设备的服务
#pragma mark 发现服务回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
 
    //NSLog(@"didDiscoverServices,Error:%@",error);
    CBService * __nullable findService = nil;
    // 遍历服务
    for (CBService *service in peripheral.services)
    {
        //NSLog(@"UUID:%@",service.UUID);
        if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"你要用的服务UUID"]])
        {
            findService = service;
        }
    }
    NSLog(@"Find Service:%@",findService);
    if (findService)
        [peripheral discoverCharacteristics:NULL forService:findService];
}


#pragma mark 发现特征回调
/**
 --  发现特征后,可以根据特征的properties进行:读readValueForCharacteristic、写writeValue、订阅通知setNotifyValue、扫描特征的描述discoverDescriptorsForCharacteristic。
 **/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    for (CBCharacteristic *characteristic in service.characteristics) {
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"你要用的特征UUID"]]) {
 
            /**
             -- 读取成功回调didUpdateValueForCharacteristic
             */
            self.characteristic = characteristic;
            // 接收一次(是读一次信息还是数据经常变实时接收视情况而定, 再决定使用哪个)
//            [peripheral readValueForCharacteristic:characteristic];
            // 订阅, 实时接收
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
 
            // 发送下行指令(发送一条)
            NSData *data = [@"硬件工程师给我的指令, 发送给蓝牙该指令, 蓝牙会给我返回一条数据" dataUsingEncoding:NSUTF8StringEncoding];
            // 将指令写入蓝牙
                [self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }
        /**
         -- 当发现characteristic有descriptor,回调didDiscoverDescriptorsForCharacteristic
         */
        [peripheral discoverDescriptorsForCharacteristic:characteristic];
    }
}


  1. 从外围设备读取数据
#pragma mark - 获取值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    // characteristic.value就是蓝牙给我们的值(我这里是json格式字符串)
    NSData *jsonData = [characteristic.value dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
    // 将字典传出去就可以使用了
}


#pragma mark - 中心读取外设实时数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (characteristic.isNotifying) {
        [peripheral readValueForCharacteristic:characteristic];
    } else { 
        NSLog(@"Notification stopped on %@.  Disconnecting", characteristic);
        NSLog(@"%@", characteristic);
        [self.centralManager cancelPeripheralConnection:peripheral];
    }
}

  1. 给外围设备发送(写入)数据

// 上文中发现特征之后, 发送下行指令的时候其实就是向蓝牙中写入数据
// 例:
// 发送检查蓝牙命令
- (void)writeCheckBleWithBle
{
    _style = 1;
    // 发送下行指令(发送一条)
    NSData *data = [@"硬件工程师提供给你的指令, 类似于5E16010203...这种很长一串" dataUsingEncoding:NSUTF8StringEncoding];
    [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}



#pragma mark 数据写入成功回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    NSLog(@"写入成功");
    if ([self.delegate respondsToSelector:@selector(didWriteSucessWithStyle:)]) {
        [self.delegate didWriteSucessWithStyle:_style];
    }
}

  1. 停止扫描

#pragma mark 停止扫描外设
- (void)stopScanPeripheral{
    [self.centralManager stopScan];
}


#pragma mark 扫描外设
- (void)scanDevice
{
    if (_centralManager == nil) {
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    [_deviceDic removeAllObjects];
     }
}

  1. 断开连接
#pragma mark 断开连接
- (void)disConnectPeripheral{
    /**
     -- 断开连接后回调didDisconnectPeripheral
     -- 注意断开后如果要重新扫描这个外设,需要重新调用[self.centralManager scanForPeripheralsWithServices:nil options:nil];
     */
    [self.centralManager cancelPeripheralConnection:self.peripheral];
}