程序员人生 网站导航

ios蓝牙开发(二)ios连接外设的代码实现

栏目:综合技术时间:2017-02-23 09:11:00

上1篇文章介绍了蓝牙的技术知识,这里我们具体说明1下中心模式的利用场景。主装备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的利用。1般来讲,外设(蓝牙装备,比如智能手环之类的东西),会由硬件工程师开发好,并定义好装备提供的服务,每一个服务对的特点,每一个特点的属性(只读,只写,通知等等),本文例子的业务场景,就是用1手机app去读写蓝牙装备。


ios连接外设的代码实现流程

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)

准备环境

  1 xcode
  2 开发证书和手机(蓝牙程序需要使用使用真机调试,使用摹拟器也能够调试,但是方法很蛋疼,我会放在最后说)
  3 蓝牙外设

实现步骤

1 导入CoreBluetooth头文件,建立主装备管理类,设置主装备拜托

#import <CoreBluetooth/CoreBluetooth.h>
    @interface ViewController : UIViewController<CBCentralManagerDelegate>


    @interface ViewController (){
        //系统蓝牙装备管理对象,可以把他理解为主装备,通过他,可以去扫描和链接外设
        CBCentralManager *manager;
        //用于保存被发现装备
        NSMutableArray *peripherals;
    }

    - (void)viewDidLoad {
        [super viewDidLoad];
        /*
         设置主装备的拜托,CBCentralManagerDelegate
            必须实现的:
            - (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主装备状态改变的拜托,在初始化CBCentralManager的合适会打开装备,只有当装备正确打开后才能使用
            其他选择实现的拜托中比较重要的:
            - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外设的拜托
            - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的拜托
            - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的拜托
            - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的拜托
        */
         //初始化并设置拜托和线程队列,最好1个线程的参数可以为nil,默许会就main线程
         manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];

2 扫描外设(discover),扫描外设的方法我们放在centralManager成功打开的拜托中,由于只有装备成功打开,才能开始扫描,否则会报错。

 -(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");
                    //开始扫描周围的外设
                    /*
                     第1个参数nil就是扫描周围所有的外设,扫描到外设后会进入
                          - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
                     */
                    [manager scanForPeripheralsWithServices:nil options:nil];

                    break;
                default:
                    break;
            }

        }

        //扫描到装备会进入方法
        -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{

            NSLog(@"当扫描到装备:%@",peripheral.name);
            //接下来可以连接装备

        }

3 连接外设(connect)

  //扫描到装备会进入方法
  -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{

            //接下连接我们的测试装备,如果你没有装备,可以下载1个app叫lightbule的app去摹拟1个装备
            //这里自己去设置下连接规则,我设置的是P开头的装备
                   if ([peripheral.name hasPrefix:@"P"]){
                   /*
                       1个主装备最多能连7个外设,每一个外设最多只能给1个主装备连接,连接成功,失败,断开会进入各自的拜托
                    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的拜托
                    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的拜托
                    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的拜托
                    */

                    //找到的装备必须持有它,否则CBCentralManager中也不会保存peripheral,那末CBPeripheralDelegate中的方法也不会被调用!!
                    [peripherals addObject:peripheral];
                    //连接装备
                   [manager connectPeripheral:peripheral options:nil];
               }

        }


        //连接到Peripherals-成功
        - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
        {
            NSLog(@">>>连接到名称为(%@)的装备-成功",peripheral.name);
        }

        //连接到Peripherals-失败
        -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
        {
            NSLog(@">>>连接到名称为(%@)的装备-失败,缘由:%@",[peripheral name],[error localizedDescription]);
        }

        //Peripherals断开连接
        - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
            NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);

        }

有1点非常容易出错,大家请注意。在 didDiscoverPeripheral这个拜托中有这1行

//找到的装备必须持有它,否则CBCentralManager中也不会保存peripheral,那末CBPeripheralDelegate中的方法也不会被调用!!
    [peripherals addObject:peripheral];

请特别注意,如果不保存,会影响到后面的方法履行,这个地方很多人出错,在我的蓝牙交换群中每天几近都会由于这个问题致使没法连接和对外设后续的操作。

大家也能够看1下这个拜托在xcode中的说明,重点看@discussion中的内容,里面特别指出了需要retained对象。

/*!
 *  @method centralManager:didDiscoverPeripheral:advertisementData:RSSI:
 *
 *  @param central              The central manager providing this update.
 *  @param peripheral           A <code>CBPeripheral</code> object.
 *  @param advertisementData    A dictionary containing any advertisement and scan response data.
 *  @param RSSI                 The current RSSI of <i>peripheral</i>, in dBm. A value of <code>127</code> is reserved and indicates the RSSI
 *								was not available.
 *
 *  @discussion                 This method is invoked while scanning, upon the discovery of <i>peripheral</i> by <i>central</i>. A discovered peripheral must
 *                              be retained in order to use it; otherwise, it is assumed to not be of interest and will be cleaned up by the central manager. For
 *                              a list of <i>advertisementData</i> keys, see {@link CBAdvertisementDataLocalNameKey} and other similar constants.
 *
 *  @seealso                    CBAdvertisementData.h
 *
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;

4扫描外设中的服务和特点(discover)

装备连接成功后,就能够扫描装备的服务了,一样是通过拜托情势,扫描到结果后会进入拜托方法。但是这个拜托已不再是主装备的拜托(CBCentralManagerDelegate),而是外设的拜托(CBPeripheralDelegate),这个拜托包括了主装备与外设交互的许多回叫方法,包括获得services,获得characteristics,获得characteristics的值,获得characteristics的Descriptor,和Descriptor的值,写数据,读rssi,用通知的方式定阅数据等等。

4.1获得外设的services

//连接到Peripherals-成功
        - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
        {
            NSLog(@">>>连接到名称为(%@)的装备-成功",peripheral.name);
            //设置的peripheral拜托CBPeripheralDelegate
            //@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
            [peripheral setDelegate:self];
            //扫描外设Services,成功后会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
            [peripheral discoverServices:nil];

        }
        //扫描到Services
        -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
            //  NSLog(@">>>扫描到服务:%@",peripheral.services);
            if (error)
            {
                NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
                return;
            }

            for (CBService *service in peripheral.services) {
                             NSLog(@"%@",service.UUID);
                             //扫描每一个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
                             [peripheral discoverCharacteristics:nil forService:service];
                         }
        }

### 4.2获得外设的Characteristics,获得Characteristics的值,获得Characteristics的Descriptor和Descriptor的值

//扫描到Characteristics
     -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
         if (error)
         {
             NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
             return;
         }

         for (CBCharacteristic *characteristic in service.characteristics)
         {
             NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
         }

         //获得Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         for (CBCharacteristic *characteristic in service.characteristics){
             {
                 [peripheral readValueForCharacteristic:characteristic];
             }
         }
         //搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         for (CBCharacteristic *characteristic in service.characteristics){
             [peripheral discoverDescriptorsForCharacteristic:characteristic];
         }
     }

    //获得的charateristic的值
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        //打印出characteristic的UUID和值
        //!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
        NSLog(@"characteristic uuid:%@  value:%@",characteristic.UUID,characteristic.value);

    }

    //搜索到Characteristic的Descriptors
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{

        //打印出Characteristic和他的Descriptors
         NSLog(@"characteristic uuid:%@",characteristic.UUID);
        for (CBDescriptor *d in characteristic.descriptors) {
            NSLog(@"Descriptor uuid:%@",d.UUID);
        }
    }
    //获得到Descriptors的值
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
        //打印出DescriptorsUUID 和value
        //这个descriptor都是对characteristic的描写,1般都是字符串,所以这里我们转换成字符串去解析
        NSLog(@"characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
    }

5 把数据写到Characteristic中

//写数据
    -(void)writeCharacteristic:(CBPeripheral *)peripheral
                characteristic:(CBCharacteristic *)characteristic
                         value:(NSData *)value{

        //打印出 characteristic 的权限,可以看到有很多种,这是1个NS_OPTIONS,就是可以同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不同的通知方式。
        /*
         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
         };
         */
        NSLog(@"%lu", (unsigned long)characteristic.properties);
        //只有 characteristic.properties 有write的权限才可以写
        if(characteristic.properties & CBCharacteristicPropertyWrite){
            /*
                最好1个type参数可以为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区分是是不是会有反馈
            */
            [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }else{
            NSLog(@"该字段不可写!");
        }
    }

6 定阅Characteristic的通知

//设置通知
    -(void)notifyCharacteristic:(CBPeripheral *)peripheral
                characteristic:(CBCharacteristic *)characteristic{
        //设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];

    }

    //取消通知
    -(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
                 characteristic:(CBCharacteristic *)characteristic{

         [peripheral setNotifyValue:NO forCharacteristic:characteristic];
    }

7 断开连接(disconnect)

//停止扫描并断开连接
    -(void)disconnectPeripheral:(CBCentralManager *)centralManager
                     peripheral:(CBPeripheral *)peripheral{
        //停止扫描
        [centralManager stopScan];
        //断开连接
        [centralManager cancelPeripheralConnection:peripheral];
    }

8 摹拟器蓝牙调试,慎用,最好还是用真机去调试。

    由于在iPhone 4s以后的iOS才支持BLE,新1代的这些iOS装备又都不便宜,在做测试的时候,用iOS摹拟器进行调试,可以节俭1些开发本钱。怎样在iOS摹拟器上调试BLE,
    苹果最初给出的说明是,支持BLE的mac机子上可以用摹拟器进行调试,并给出了1份技术文档(传送门),恶心的是,后来苹果抽风,又把这份文档移除,
    并且把iOS 7.0的摹拟器上对BLE的支持也移除掉了(难道是想让大家多买装备测试?Apple sucks.)后面,网上搜了1下,解决办法以下:
    1. 买1个CSR蓝牙4.0 USB适配器(某宝上大概30块钱),在机子上插入该物(你懂的)
    2. 在Terminal下敲入sudo nvram bluetoothHostControllerSwitchBehavior="never" , 重启Mac。
    3. 用XCode 4.6调试代码,在iOS 6.1的摹拟器上跑程序(用XCode 5.0跑iOS 7.0摹拟器会抛异常,缘由上面详诉过了,Apple sucks,你懂的)

    如何下降摹拟器的IOS版本呢?
    XCode->Preferences->Downloads里面有很多simulators你可以下载
    选择个6.1的下载好了


我博客中大部份示例代码都上传到了github,地址是:https://github.com/coolnameismy/demo,点击跳转代码下载地址

本文代码寄存目录是BleDemo

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐