iOS蓝牙

作者: 玉米地里种玉米 | 来源:发表于2018-09-13 21:07 被阅读66次

    耗电量高、覆盖范围小

    • MFI make for iPad、iPhone、iTouch 专们为苹果设备制作的设备,开发使用ExternalAccessory 框架(认证流程貌似挺复杂的,而且对公司的资质要求较高)。
    • BLE Bluetooth low energy,蓝牙4.0设备因为低耗电,所以也叫做BLE,开发使用CoreBluetooth 框架(因为苹果系统与设备的更新,当下苹果设备蓝牙都是支持蓝牙4.0[BLE(Bluetooth low energy)]

    两种模式

    1. CBCentralMannager 中心模式 :以手机(app)作为中心,连接其他外设的场景
    2. CBPeripheralManager 外设模式:使用手机作为外设连接其他中心设备操作的场景

    Core Bluetooth

    难点在于理解其工作模式和理清一些关键概念,比如 Peripheral, Central, Service, characteristics等等。
    不要被这些陌生的单词吓到,网络协议的应用大多脱不了CS的架构模型,这里和大家一起对照传统的Client/Server架构来梳理下iOS和OSX上CoreBluetooth的重要知识点。借鉴一张图,方便大家一目了然的明白CoreBluetooth的工作原理。

    image

    Peripheral作为Server,Central作为Client,Peripheral广播自己的Service和characteristic,Central订阅某一个具体的characteristic,Peripheral就和Central之间通过characteristic建立了一个双向的数据通道,整个模型非常简洁而且符合我们CS的架构体系。

    确定角色

    首先值得开心一把的是iOS和OSX使用的是同一套API封装,都是基于CoreBluetooth Framework,只在极细小的地方有些差异,完全可以做一层library的封装在两个平台上无缝衔接使用
    在具体搭建基于CoreBluetooth应用之前,要先确立到底哪一方作为Peripheral,哪一方又是Central。MacBook,iPhone,iPad都能成为Peripheral或者Central。我们通过代码的方式再看一遍上面的架构流程

    Server端

    创建Peripheral,也就是我们的Server:

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

    生成Service以备添加到Peripheral当中:

    CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID] primary:YES];
    

    生成characteristics以备添加到Service当中:

    self.transferCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]
                                                            properties:CBCharacteristicPropertyNotify|CBCharacteristicPropertyWrite
                                                                        value:nil
                                                                        permissions:CBAttributePermissionsReadable|CBAttributePermissionsWriteable];
    

    建立Peripheral,Server,characteristics三者之间的关系并开始广播服务:

    //建立关系
    transferService.characteristics =@[self.transferCharacteristic];
    [self.peripheral addService:transferService];
    //开始广播
    [self.peripheral startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
    

    Client端
    创建我们的Central,也就是client:

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

    扫描可用的Peripheral:

    [self.central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]
                                             options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
    

    扫描到Peripheral之后连接:

    [self.central connectPeripheral:targetPeripheral options:nil];
    

    连接成功之后查找可用的Service:

    [peripheral discoverServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
    

    找到Service之后,进一步查找可用的Characteristics并订阅:

    //查找Characteristics

    [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]] forService:service];
    

    查找到Characteristics订阅:

    //订阅

    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    

    订阅之后Central和Peripheral之间就建立了一个双向的数据通道,后续二者之间的数据传输就可以通过characteristic来完成了。

    数据传输

    订阅之后Central和Peripheral之间就建立了一个双向的数据通道,后续二者之间的数据传输就可以通过characteristic来完成了。

    有了数据通道,接下来就是如何传输数据了。说到数据传输就免不了要确定应用层的协议,类似平时我们使用socket实现游戏的网络模块时,需要自定义应用层协议才能实现业务数据的交换,协议的设计这里就不展开说了,之前有过相关经验的童鞋完全可以把协议层迁移过来。

    再看下Peripheral是如何向Central发送数据的,首先Peripheral会向自己的characteristic写数据:

    [self.peripheral updateValue:chunk forCharacteristic:self.transferCharacteristic onSubscribedCentrals:@[self.central]];
    

    Central那一端会通过如下回调收到来自Peripheral的数据流:

    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
    

    这里值得注意的是二者数据的发送与获取,是以二进制流的方式发送的,是NSData的形式封装的,Peripheral可以持续不停的发送二进制流,所以Central端收到的时候需要自己做协议的解析,根据自定义协议将整个流拆成一个个的业务Packet包。

    而Central发送的时候确是封装成了一个个的Request,比如Central端调用如下API发送数据

    [self.discoveredPeripheral writeValue:data forCharacteristic:self.discoveredCharacterstic type:CBCharacteristicWriteWithoutResponse];
    

    Peripheral端会收到如下回调:

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

    数据被封装成了单独的CBATTRequest,直接去Request当中取value就可以获取到Central所发送过来的数据。

    遇到的问题:

    我之前测试协议的时候发现一个不大不小的坑,多个Central(比如A和B)端同时一个Peripheral发送数据的时候,Peripheral会收到多个CBATTRequest,奇怪的是每个CBATTRequest当中的Central都会指向最先建立连接的A,结果导致Peripheral端无法判断write请求的数据来自哪一个Central。

    蓝牙不仅仅能应用于穿戴式设备,还能做一些好玩的小众应用或者游戏,其本质是一个小型封闭的局域网,不用经过第三方的Server或者Cloud,很安全。

    Demo: https://github.com/laoyou2018/BTLE.git

    相关文章

      网友评论

          本文标题:iOS蓝牙

          本文链接:https://www.haomeiwen.com/subject/knvggftx.html