iOS 蓝牙开发·基础篇

基础概念

蓝牙通信基于 client-server 构架,由两个角色进行通信,分别是 CentralPeripheral,中文环境可以理解为主设备从设备

Peripheral 角色主要作为数据的提供方,比如 Apple Watch;Central 主要作为数据需求方,通常为 iPhone/iPad 等产品。

Peripheral 提供一系列的服务(service),每种服务含有一个或多个特征属性(characteristics)。

两种角色要相互通信,双方就必须要有唯一的标识。

iOS 提供 CoreBuletooth 框架,作为蓝牙 4.0 通信的基础。对应每一个 Peripheral,CoreBuletooth 框架会为其分配一个 UUID,据测试,一般情况下,这个 UUID 在一个 iOS 设备上是不会改变的,但对于不同的 iOS 设备,拿到的 UUID 却又不同。Central 的 ID 由底层框架维护,上层并不知晓。

在通信时,一方向外界广播数据,并携带接收方的 ID,周边所有蓝牙设备都会收到消息,如果发现自己与该 ID 匹配,则处理消息,否者忽略。

在建立连接前,需要知道周边设备的 UUID,需要一个发现的过程。

发现

未连接的蓝牙设备,会不断的向周边广播数据。数据中携带它具有的服务(Service),名称,信号强度,厂商信息等。在 iOS 中以 CBPeripheral 类表示。

中心设备通过发现的方式来找到周边设备进而建立连接通信。

CBCentralManager 类代表 iOS 设备,开始使用前,先进行初始化操作。

let centralManager = CBCentralManager()
centralManager.delegate = self
复制代码

CBCentralManager初始或会驱动硬件,这个过程是异步的,如果驱动成功/失败,会通过代理方法告知:

func centralManagerDidUpdateState(_ central: CBCentralManager)
复制代码

提示:如果代理方法没有调用,控制台输出 [CoreBluetooth] XPC connection invalid,很可能是 centralManager 实例被释放了。详细参考XPC Error CoreBluetooth | Apple Developer Forums

Central 进入 powerOn 状态后,才可执行扫描:

centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
复制代码

如果清楚自己所需的蓝牙设备服务,withServices 参数传入具体的服务 UUID 列表,如果传 nil 默认发现所有设备。options用于指定发现的规则,比较常用的是 CBCentralManagerScanOptionAllowDuplicatesKey,默认情况下,中心设备每收到一个包,就是调用一次 Delegate 方法,设置为 false,会每隔一段时间接收一次周边设备的广播信号,如果只是为了发现连接设备,这样设置可以节省电量以及提高 App 的性能。

centralManager 发现的设备的回调:

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        // peripheral:发现的设备
        // advertisementData: 广播数据字典
        // RSSI: 信号强度
}
复制代码

广播数据包含的内容在Advertisement Data Retrieval Keys | Apple Developer Documentation。需要注意的是,这里含有的名称字段和peripheral.name的值并不一定相同。后者是前者的系统级缓存,并且没有给出缓存失效时间,如果蓝牙设备中途修改过名称,读到的很可能是失效的缓存,真实的名称在 CBAdvertisementDataLocalNameKey 中。虽然修改名称的可能性很小,但是难免也会有人遇到这个坑。

需要注意的是,发现的设备必须主动去持有,否者在超出作用域就释放了。

连接

发现目标设备后,连接设备。

central.connect(peripheral, options: nil)
复制代码

如果设备连接成功,回调代理方法:

optional public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
复制代码

如果连接失败,回调:

optional public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
复制代码

该方法也会在设备超出连接返回的时候调用。

在 iOS 中,存在系统级的蓝牙连接方式,叫做配对。配对的设备可在设置->蓝牙->我的设备 查看。配对的设备,即使 kill 应用程序,底层仍会持有连接的状态,CBCentralManager 提供获取已配对设备的方法。

open func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> [CBPeripheral]
复制代码

不过,需要提供获取已连接设备的 serviceUUIDs,无论是哪个应用程序配对的蓝牙设备只要具有对应的服务都会被获取。

最后,获取到的设备只是底层连接,上层应用仍然需要调用 connectPeripheral:options:方法,等待centralManager:didConnectPeripheral: 回调,使其进入连接状态。

建立过连接的设备,系统都会缓存它的信息,只要它的硬件唯一标识没有修改,系统为其分配的 identifier 会始终不变。所以,建立过连接的设备,可通过保存它的 identifier 来再次获取,并建立连接。

open func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheral]
复制代码
发现服务和特征

建立连接后,需要发现该蓝牙设备所具有的服务,每个服务有对应的 UUID。

peripheral.discoverServices(nil)
复制代码

一般蓝牙设备具有多种服务, 如果希望发现所有的服务,传 nil。在开发中,我们清楚所需要的服务类型,最好传入具体的服务 UUID。成功发现服务会回调 CBPeripheralDelegate 的如下方法:

optional public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
复制代码

服务类型保存在 services 属性中。

发现目标服务后,下一步是该发现服务中的具体特征(Characteristics),因为数据的读写对象是它。

peripheral.discoverCharacteristics(nil, for: service)
复制代码

同样特征也具有 UUID,如果传 nil 将获取到该服务下的所有特征,当然如果知道目标特征的 UUID,传对应的 ID 能够提高效率。特征发现成功后,会有如下回调:

optional public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
复制代码

目标特征保存在 service.characteristics 中,接着可以对它们进行读写操作。

读特征值

单一的数据存储在 Service 的 Characteristic 中,比如说温度。

有两种方式读取 Characteristic 中的数据:

  1. 直接读取
open func readValue(for characteristic: CBCharacteristic)
复制代码

如果读取到数据,回调代理方法:

optional public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
复制代码

数据存储于 charateristic.value中。

并不是所有的特征都是可读的,需提前检查检查 CBCharacteristicproperties 属性。确保它包含 CBCharacteristicPropertyRead,如果不包含,上述代理方法会返回错误。

  1. 订阅通知

有些情况下,特征值会动态的变化,如果都需要手动去获取效率会比较低。Characteristic 还有一个被订阅的功能,订阅之后一旦特征值发生变化就会通过回调方法通知 App。

peripheral.setNotifyValue(true, for: characteristic)
复制代码

订阅成功与否的状态同样通过代理方法返回:

optional public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?)
复制代码

当然并不是所有的 Characteristic 都有被订阅的功能。同样需要检查properties属性,包含 Notify 或者 Indicate才能被订阅。

写特征值

有时候还需要向 Peripheral 发送数据,实际上是对它的某个特征值进行写操作,写操作有两种形式,有回复和没有回复。

peripheral.peripheral.writeValue(data, for: characteristic, type: .withResponse)
复制代码

.withResponse 表示有回复的类型,回复以代理的形式通知:

optional public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?)
复制代码

如果写入操作失败,错误信息保存在 error 中。

.withoutResponse 不会有回复,只会尽最大努力的写,但无法保证能写成功,如果写失败,不会有任何通知和错误信息。

传入的 data 数据,在内部会被拷贝,后续修改或者释放影响写操作。

同样,并不是所有的特征都具有写的功能,在写之前需检查该特征的 properties 属性是否包含 write 或者 writeWithoutResponse

转载于:https://juejin.im/post/5a5dd9be6fb9a01ca2678060

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值