几年前,当我在一个正在运行的项目中首次遇到蓝牙时,我找到了这篇文章,这对理解它的工作原理有很大帮助,它找到了一个“起点”。 希望对初学者有用。
关于作者:Yoav Schwartz是哥本哈根驴友共享系统Donkey Republic的领先iOS开发人员,致力于改变人们对自行车的态度。 接下来,我们将代表作者发言。
在本文中,我将讨论使用CoreBluetooth的实用技术。 首先是关于蓝牙低功耗(BLE)的问题,因为并不是每个人都熟悉这项技术,然后是关于CoreBluetooth(苹果的框架,它使我们能够与BLE设备进行交互)。 我还将告诉您一些我自己在调试,哭泣和扯掉头发时学到的开发技巧。
低功耗蓝牙
首先,什么是BLE? 有点像蓝牙,我们都在扬声器,耳机等中使用蓝牙,但是有一个区别-该协议消耗的功率很小。 通常,支持BLE的设备的单节电池充电可以持续数月甚至数年(当然,这取决于使用方式)。 这使我们能够做以前“普通”蓝牙无法使用的事情。 该标准称为蓝牙4.0,所有标准均始于称为智能蓝牙的技术,该技术后来发展为BLE。 有一本200页的
手册 ,您可以阅读就寝时间,令人兴奋的阅读。
BLE在能耗方面非常经济,并且协议本身也不是很复杂。 那为什么会流血呢? 我们如何使用它? 第一个也是最常见的示例是心率传感器。 通常,此设备通过协议测量并传输您的心率。 您还可以通过BLE连接各种传感器,并读取它们收集的数据。 最后,还有一些iBeacon可以告诉您“靠近”某个地方。 用引号引起来,因为Apple在iPhone上阻止了将iBeacons检测为常规蓝牙设备的功能,因此我们必须使用CoreLocation。 通常,这就是物联网:您可以连接到电视或空调,并使用此协议与之通信。
如何运作?
我们有一个perifer-这些是使用蓝牙协议的设备。 每个外围设备都有服务,可以有任意数量,并且每个都有特征。 您可以将perifer视为服务器。 随之而来的是所有后果:有时关闭电源,有时需要花费一些时间来传输数据,有时根本没有数据。
通常,我们提供的服务具有许多特征,每个特征都包含一个值,类型等。 要使用CoreBluetooth,您不需要一无所知,最重要的是读取数据。 这就是我们为了自己的目的而试图获取,修改或使用的东西。 我们需要这些数据,并了解如何使用它。
这是BLE的快速介绍,因为有成千上万的资源比我能更好地解释技术功能。
核心蓝牙
苹果很早以前就在iOS 5中引入了CoreBluetooth。Apple开始研究将BLE引入其设备的时间要早于Android和技术的日益普及。 许多开发人员在他们的应用程序中使用此框架,因为BLE协议本身非常复杂,所以它基本上只是一个包装。 并非如此,但请相信我,这不是我每天都想工作的事情。 就像许多其他事物一样,Apple将其包装在美观,方便的包装中,使您可以使用愚蠢的开发人员都能理解的术语。
现在该轮到您了,您真正需要了解与框架进行通信所涉及的类的知识。 我们的主要角色是CBCentralManager,创建它:
manager = CBCentralManager(delegate:self, queue:nil, options: nil)
在上面,我们创建了一个新的经理,指示他的代表,否则我们将无法使用它。 我们还指示队列,在本例中为nil,这意味着与管理器的所有通信都将在主队列上进行。
您需要了解您将要做什么—使用单独的队列会使应用程序复杂化,但是,当然,用户会更爱您。 如果您打算仅与一台设备进行通信,则无法打扰并使用主队列。 如果您仍然想进行实验,请创建一个队列,在构造函数中指定该队列,并且不要忘记在使用其他结果之前返回主队列。
选件 这里没有什么特别有趣的,也许最主要的是-当您创建一个管理器并且为用户关闭了蓝牙时-该应用程序会告诉他有关信息,但是几乎每个人都单击“确定”(实际上并不包括蓝牙),这就是为什么我也使用此选项我不使用。
首先,在创建管理器之后,委托调用方法:
func centralManagerDidUpdateState(_ central: CBCentralManager)
因此,我们从硬件获得响应-用户是否启用了蓝牙。
第一个提示:在我们得到蓝牙已打开的答案之前,管理器是无用的,其状态为PoweredOn。 其他状态只能用于要求用户打开蓝牙。
设备搜索
现在我们的经理工作正常,我们可以观察,
我们周围是什么(在收到.PoweredOn状态后,我们将其称为scanForPeripheralsWithServices函数:)
manager.scanForPeripheralsWithServices([CBUUID], options: nil)
对于服务,这是一个CBUUID数组(一个类,是蓝牙低功耗大约Per。Per。使用的属性的128位通用唯一标识符),我们用它作为过滤器来查找仅具有这组UID的设备,这是CoreBluetooth中的常见做法。
如果您将nil作为参数传递,我们可以看到周围的所有设备。 当然,为了提高性能,最好指定我们需要的参数数组,但是在您不知道它们的情况下,如果传递nil不会发生任何可怕的事情,没有人死亡。
自启动搜索设备以来,我们必须停止搜索。 否则,搜索将继续并降落用户的电池,直到我们将其停止为止。 一旦找到合适的设备,或者搜索需求消失了,我们将停止:
manager.stopScan()
每次发现新设备时,管理器委托都会在初始化时指定的队列上调用didDiscoverPeripheral函数。 该功能向我们发送找到的设备(外围设备),有关该设备的信息(advertiseData-芯片开发者每次决定显示的信息)以及相对RSSI信号电平(以分贝为单位)。
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
第二个技巧:始终与检测到的Perifer保持牢固的联系。 如果不这样做,系统将决定我们不需要找到的设备,并将其丢弃。 她会记住他,但我们将不再与他接触。 否则,我们将无法使用该设备。
设备连接
我们找到了我们感兴趣的设备-这是参加聚会和见到漂亮女孩的方法。 我们想要连接,我们称之为connectPeripheral函数-我们提供“购买饮料”。 因此,我们尝试连接到所需的设备(外围设备),它可以告诉我们“是”或“否”,但是我们的iPhone确实不错,因此我们会听到肯定的答案。
manager.connectPeripheral(peripheral, options: nil)
在这里,我们求助于负责连接的经理,告诉他我们要连接到的特定设备,然后我们对选项一律不作任何规定(如果您真的非常想了解选项,请阅读文档,但通常可以不用它们)。 使用完设备后,您可以在早上断开与设备的连接-cancelPeripheralConnection:
//called to cancel and/or disconnect manager.cancelPeripheralConnection(peripheral)
连接或断开连接后,代表将通知我们:
//didConnect func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) //didDisconnect func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!)
现在,还有两个重要的提示。 蓝牙协议假定连接超时,但是Apple不在乎。 iOS会尝试一次又一次地连接,并且直到您调用cancelPeripheralConnection才会停止。 此过程可能花费太长时间,因此有必要限制它的时间,如果最终我们没有收到成功的连接消息(didConnectPeripheral),则需要通知用户出了点问题。
如果您未保持与外围设备的牢固链接,iOS只会重置连接。 从她的角度来看,这将意味着您不需要它,并且维护它是电池的能源密集型任务,而且我们知道Apple与能耗之间的关系。
让设备变得有用
因此,我们已连接到设备,让我们对其进行操作。 之前,我提到了服务和功能,它们所包含的价值,这就是我们所需要的。 现在我们有了一个设备,它已连接,并且可以通过调用外围设备.discoverServices获得其服务。
peripheral.discoverServices(nil) func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) peripheral.services
现在听起来有些混乱,但是尽管创建了管理器,但在我们创建管理器时定义的线程上调用了该代理。 也就是说,系统会记住它与哪个流一起使用,并且我们所有的蓝牙通信都在该流上进行。 重要的是,如果您不使用它,不要忘记返回到主菜单。
我们获得了服务,但仍然没有任何合作可做。 接下来,您需要调用peripheral.discoverCharacteristics,该委托将在didDiscoverCharacteristicsForService中为我们提供已接收服务的所有可用特征。 现在我们可以读取值了
其中包含(readValueForCharacteristic)或要求在其中发生更改时立即通知我们-setNotifyValue。
peripheral.discoverCharacteristics(nil, forService: (service as CBService)) func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) peripheral.readValueForCharacteristic(characteristic) peripheral.setNotifyValue(true, forCharacteristic: characteristic) func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!)
与Android不同,Apple不区分阅读和通知。 也就是说,我们不知道发生了什么-我们正在从设备中读取某些内容,或者该设备正在告诉我们一些内容。
录制到设备
我们有一台设备,我们从中读取信息,进行管理。 因此,我们通常可以在其上记录信息-普通NSData。 您只需要了解此设备对我们的期望以及它将接受什么。
大多数BLE设备都带有规范,一种API,从中可以清楚地知道如何与它们“通信”。 您可以从特性中提取数据,以至少大致了解设备对我们的期望。
从规范中,我们可以了解哪些属性读取了哪些属性,以及写入了哪些属性,是否会收到有关更改的通知(isNotifying)。 通常,我们会在这里找到工作所需的一切。
peripheral.writeValue(data: NSData!, forCharacteristic: CBCharacteristic!, type: CBCharacteristicWriteType) characteristic.properties - OptionSet type characteristic.isNotifying func peripheral(peripheral: CBPeripheral!, didWriteValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!)
在记录过程中,代表将通知我们一切进展顺利(didWriteValueForCharacteristics),所需值已更新,我们可以将其告知用户或以其他方式使用此信息。
我们仅依靠Apple的实施方式在一个非常狭窄的部分中讨论该主题,因此必须解决许多问题。 例如,非常强烈地依赖代表团,因此深受苹果的喜爱。
CBPeripheral的继承? 如果一切都那么容易
看来,既然有了设备,就可以开始使用它了,但实际上它并不能告诉我们任何有关其自身的信息。 也许我们想控制锁,空调或心率传感器。 您需要知道我们正在与哪个设备通信。
看起来像继承:我们有一个共同点的特例。 根据我的经验,我可以说使用继承时,某些功能根本无法按预期工作,某些功能完全无法工作,而且您也不知道为什么。 通常,我会警告您不要继承CBPeripheral。 怎么办
我建议您将CBPeripheral添加到将对其进行管理的对象的构造函数中。 它将其封装在此类中。 使用它与设备交互,保持牢固的链接,以使iOS不会断开连接。 但是最重要的是,该类将用作委托,否则将很难在一个地方管理所有设备,否则将威胁很多。
连接并使用CBPeripheralDelegate
因此,我们连接到该设备并希望成为CBPeripheralDelegate。 还有一个细微差别:使用设备,“询问”其服务和特性,对其进行读写时,几乎所有通信都与外围设备进行。 除连接外的所有内容。
自然,我们希望将所有通信都集中在一个地方,但是管理员应该知道设备发生了什么。 困难在于拥有一个真实的来源,以确保每个人都及时了解设备发生的情况。 为此,我们将监视外围设备的状态-它可以从断开,连接和连接状态更改。 它总是告诉您当前的情况。 仍然需要订阅我们控制设备中的状态更改(我之前已经提到过),这将使从一个地方与设备进行通信成为可能。
接近度
非常重要的一点,因为很难找到有关此主题的常规文档。 对于Apple及其iBeacons,一切都很简单,它们告诉我们距蓝牙设备有多近。
不幸的是,我们没有这么简单的方法来使用第三方设备。 而且不止一次,需要确定最近的设备。 也很难理解设备是否在可用范围内。 有时,在搜索设备时,它只会让您了解一次自己,然后消失,然后尝试进行连接将失败。
我们使用以下方法:为DiscoverPeripheral中收到的每条消息保存带有日期和信号强度(RSSI)标签的堆栈。 如果有人遇到CoreLocation,我们的方法类似于在其中存储时间戳和相应坐标的方法。 通常,信号(RSSI)越高,设备越近。 很难理解设备是否在可访问范围内,部分原因是该概念本身非常灵活。 为此,我使用信号的加权平均值。 请记住,每次需要了解所连接设备的信号强度时,都必须手动请求它。
接下来是什么?
不幸的是,如果您也阅读本文,也不会使您成为专家。
变得很有趣-注意
Apple的CoreBluetooth编程指南 ,该指南不是很大,但是非常有用。 WWDC 2012仍然有一些广播(
基本和
高级 ),
2013年还有一个,但是不要担心,此后几乎没有任何变化。
Realm网站上还发布了来自
Altconf 2015的视频,其中分享了伟人和专家John Sher的经验。