最近,我们的团队提出并实现了使用Bluetooth LE技术进行空中转账的功能。 我想告诉您我们是如何做到的,以及苹果通过这些工具为我们提供了什么。 许多开发人员认为蓝牙很困难,因为它是一个相当低级的协议,并且专家不多。 但是一切并没有那么可怕,实际上,使用此功能非常简单! 那些可以使用Bluetooth LE实现的功能肯定很有趣,并且随后将在竞争对手中突出您的应用程序。

首先让我们了解它是一种什么样的技术,以及它与经典蓝牙的区别。
什么是蓝牙LE?
蓝牙开发者为何将这种技术命名为“低能耗”? 毕竟,对于每个新版本的蓝牙,功耗已经降低了很多倍。 答案就在这块电池上。
它的直径只有2 cm,容量约为220 mA * h。 工程师开发Bluetooth LE时,他们希望带有这种电池的设备能够工作数年。 他们做到了! 带有这种电池的Bluetooth LE设备可以使用一年。 像2000年那样,你们中仍有多少人以老式的方式关闭手机上的蓝牙以节省能源? 您这样做徒劳无功-每天节省的时间少于手机的10秒。 并且您禁用了非常大的功能,例如Handoff,AirDrop等。
工程师通过开发Bluetooth LE实现了什么? 他们完善了经典协议吗? 使其更节能? 刚刚优化了所有流程? 不行 他们完全重新设计了蓝牙协议栈的体系结构,并实现了这样一个事实,即现在,对于所有其他设备可见的,您只需要更少的时间就可以广播和占用信道。 反过来,这可以节省大量能源。 借助新的体系结构,现在可以对任何新设备进行标准化,这使得来自世界各地的开发人员可以与该设备进行通信,因此可以轻松编写新的应用程序来对其进行管理。 此外,自发现原理嵌入在体系结构中:连接到设备时,您无需输入任何PIN码,并且如果您的应用程序可以与此设备通信,则连接只需几毫秒。
工程师如何在能源效率方面实现如此巨大的飞跃?频率保持不变:2.4 GHz,未经认证,可在许多国家免费使用。 但是连接延迟变得更短:15-30毫秒,而不是经典蓝牙的100毫秒。 工作距离保持不变-100 m。传输间隔不很强,但发生了变化-从0.625 ms变为3 ms。
但是正因为如此,能耗无法降低十倍。 当然,一定要受苦。 这就是速度:它不是24 Mbps,而是0.27 Mbps。 您可能会说这对于2018年来说是荒谬的速度。
蓝牙LE在哪里使用?

这项技术并不年轻,它首先出现在iPhone 4s中。 并且已经设法征服了许多领域。 蓝牙LE可用于所有智能家居设备和可穿戴电子设备。 现在甚至有咖啡豆大小的芯片。
这项技术如何应用于软件?自从Apple率先将蓝牙集成到他们的设备并开始使用它以来,到现在,他们已经取得了良好的进步并将技术集成到了他们的生态系统中。 现在,您可以在AirDrop,设备快速入门,共享密码,切换等服务中满足该技术的要求。 甚至手表中的通知都是通过Bluetooth LE发出的。 此外,Apple还公开提供了有关如何确保所有应用程序的通知都发送到您自己的设备上的文档。 蓝牙LE中设备的作用是什么?
Brodcaster。 向附近的每个人发送消息,您无法连接到此设备。 按照这个原理,iBeacon和室内导航工作。
观察员 侦听周围发生的事情,仅从公共消息中接收数据。 不创建连接。
但是随着
Central和
Peripheral变得更加有趣。 为什么不将它们简单地称为服务器客户端? 从逻辑上讲,按名称判断。 但是没有
因为外围设备实际上充当服务器。 这是一种功耗较低的外围设备,可以连接到功能更强大的Central。 外围设备可以通知您它就在附近以及所提供的服务。 只有一台设备可以连接到它,并且外围设备有一些数据。 Central可以扫描空中以搜索设备,发送连接请求,连接到任意数量的设备,可以读取,写入和订阅来自Peripheral的数据。
作为开发人员,我们可以在Apple生态系统中获得什么?
对我们有什么可用?
iOS / Mac OS:- 外围和中央。
- 后台模式。
- 恢复状态。
- 连接间隔15毫秒。
watchOS / tvOS:- watchOS 4+ / tvOS 9+。
- 仅中央。
- 最多两个连接。
- Apple Watch系列2 + / AppleTv 4+。
- 进入背景时关机。
- 连接间隔30毫秒。
最重要的区别是连接间隔。 有什么影响? 要回答这个问题,您首先需要了解Bluetooth LE协议的工作原理以及绝对值如此小的差异为何非常重要。
协议如何工作
搜索和连接过程如何?
外围设备以广告间隔的频率宣布其存在,其包装很小,仅包含设备提供的一些服务标识符以及设备名称。 该间隔可能会很大,并且会根据设备的当前状态,省电模式和其他设置而有所不同。 苹果建议外部设备的开发人员将间隔的长度绑定到加速度计:如果不使用该设备,则增加间隔,如果该设备处于活动状态,则减小间隔以快速找到该设备。 Advertisement-interval与连接间隔不相关,并且由设备本身确定,具体取决于功耗及其设置。 在Apple生态系统中,它是我们无法访问和未知的;它完全由系统控制。

找到设备后,我们发送连接请求,连接间隔进入场景-第二个设备可以响应该请求的时间。 但这是在连接时,但是在读/写时会发生什么呢?

读取数据时也会出现连接间隔-将其减小2倍可提高数据传输速率。 但您需要了解,如果两个设备都不支持相同的间隔,则将选择它们中的最大值。
让我们看看外围设备传递的信息包由什么组成。
这种包装的MTU(最大传输单位)是在连接过程中确定的,并且随设备的不同而不同,具体取决于操作系统。 在协议版本4.0中,MTU约为30,有效载荷的大小不超过20个字节。 在版本4.2中,一切都已更改,现在您可以传输大约520个字节。 但是,不幸的是,只有比iPhone 5s更年轻的设备才支持此版本的协议。 与MTU的大小无关,开销的大小为7个字节:这包括ATT和L2CAP标头。 与记录一样,大体上类似的情况。

只有两种模式:有应答和无应答。 无应答模式大大加快了数据传输,因为在下一次录制之前没有等待间隔。 但是,此模式并非始终可用,并非在所有设备上也不在所有系统上都可用。 进入此记录模式的操作可能会受到系统本身的限制,因为它被认为能量效率较低。 在iOS中,有一种方法可以在记录此模式是否可用之前进行检查。
现在让我们看一下协议的组成。

该协议包括5个级别。
应用程序层是您的逻辑,在CoreBluetooth上进行了描述。
GATT(通用属性层)用于交换设备上的服务和特征。
ATT(属性层)用于管理您的特征并传输数据。
L2CAP是一种低级数据交换协议。
控制器是BT芯片本身。
您可能会问什么是关贸总协定以及我们如何使用它?关贸总协定包括功能和服务。 特征是存储数据的对象,就像变量一样。 服务是您的特征所在的组,例如名称空间。 该服务有一个名称-UUID,您可以自己选择。 服务可能包含辅助服务。

该特征还具有其自己的
UUID-实际上是一个名称。
该特性的值为NSData,您可以在此处记录和存储数据。
描述符是对特征的描述,您可以描述在该特征中期望的数据或它们的含义。 蓝牙协议中有许多描述符,但是到目前为止,Apple系统上只有两个描述符:人工描述和数据格式。 您的功能也有权限:

让我们自己尝试
我们有一个想法,可以通过空转资金而无需收款人任何东西。 想象一下,您正在为一个非常有趣的任务而困惑,编写了完美的代码,在这里一位同事建议去喝咖啡。 而且您对这项工作充满热情,以至于无法离开,请他为您买一杯美味的卡布奇诺咖啡。 他给您带来咖啡,您需要将钱还给他。 您可以按电话号码翻译,效果很好。 但是,这是一个尴尬的情况-您不知道他的电话号码。 嗯,像这样,您已经工作了三年,但是他们没有交换号码:)
因此,我们决定可以在不输入任何用户数据的情况下将资金转移给附近的人。 就像在AirDrop中一样。 只需选择一个用户,然后发送他需要的金额即可。 让我们看看我们需要什么。

按键映射
我们需要发件人:
- 我可以找到附近的所有设备并支持我们的服务。
- 我可以阅读详细信息。
- 而且他可以向收款人发送一条消息,说他已经成功地将钱寄给了他。
反过来,接收者必须能够通知周围的发送者他拥有必要的数据服务,并且能够接收来自发送者的消息。 我认为不应该描述银行详细信息转账的过程。 现在让我们尝试实现这一点。
首先,您需要提出我们的服务名称和特征。 如我所说,这是UUID。 我们只需生成它们并将它们保存在Peripheral和Central上,以使它们在两个设备上都相同。

您可以自由使用任何UUID,但以下形式的结尾除外:XXXXXXXX-
0000-1000-8000-00805F9B34FB-它们仅供不同公司使用。 您自己可以购买这样的号码,没有人会使用它。
费用为2500美元。
接下来,我们将需要创建经理:一个负责转移资金,另一个负责接收。 您只需要指定委托即可。 我们将传输中央,接收外围设备。 我们创建两者都是因为发送方和接收方在不同的时间可以是一个人。

现在,我们需要检测接收者并在我们的特征中写下接收者的详细信息。

首先,创建服务。 我们将注册UUID并指出它是主要的-即该服务是此设备的主要服务。 一个很好的例子:一个心率监视器,当前心率将是主要的服务,而电池状态是次要信息。
接下来,我们创建两个特征:一个特征用于阅读收款人的详细信息,第二个特征用于书写,以便接收人可以了解汇款的知识。 我们在服务中注册它们,然后将它们添加到管理器中,开始发现并指出服务的UUID,以便附近的所有设备在连接到我们之前都能找到我们的服务。 此数据放置在Central在广播过程中发送的数据包中。
收件人已准备好,请继续发送。 运行搜索并连接。

当您打开管理器电源时,我们会通过我们的服务开始搜索设备。 找到它们后,便将它们放入委托方法中并立即进行连接。 重要提示:您需要保持与所使用的所有外围设备的牢固链接,否则它们会泄漏。

成功建立连接后,我们将配置将与该设备一起使用的代表,然后从该设备获得所需的服务。

我们已成功连接到收件人,现在您需要阅读其详细信息。
连接后,我们已经要求设备提供所有服务。 并且在收到它们之后,将调用委托方法,该方法将列出此设备上可用的所有服务。 我们找到合适的,并要求其特征。 可以通过UUID在委托方法中找到结果,该方法存储要翻译的数据。 我们尝试读取它们,然后在委托方法中再次获得期望的结果。 所有服务,特征及其值都由系统缓存,因此不必每次以后都请求它们。

就是这样,我们汇款了咖啡,是时候向收件人显示漂亮的通知,以便他在其帐户上等待卢布了。 为此,您需要实现发送消息的过程。
我们从发送者那里获得了所需的特性,在这种情况下,我们从存储的值中获取了它。 但是在此之前,您需要像以前一样从设备上获取它。 然后只需将数据写入所需的特性即可。
之后,在另一台设备上,我们在委托方法中得到一个写请求。 在这里您可以读取发送给您的数据,对任何错误做出响应,例如,没有访问权限,或者此特征不存在。 一切正常,但前提是必须同时打开两个设备并且激活了应用程序。 我们需要在后台工作!

Apple允许您在后台使用蓝牙。 为此,您需要在info.plist中指出我们要在外设或中央模式下使用的模式的密钥。

接下来,在管理器中,您需要指定恢复密钥并创建委托方法。 现在我们可以使用背景模式了。 如果应用程序进入睡眠状态或已从内存中卸载,那么当您找到所需的外围设备或连接Central时,它将唤醒并使用密钥恢复管理器。

一切都很好,可以发布了。 但是,这里的设计师跑来向我们说:“我们想插入用户的照片,以便他们之间更容易找到对方。” 怎么办 根据我们的特征,您只能写大约500个字节,但是在某些设备上通常只写20 :(

更深入
为了解决这个问题,我们必须更深入。

现在,我们在GATT / ATT级别上与设备进行了交谈。 但是在iOS 11中,我们可以访问L2CAP协议。 但是,在这种情况下,您将必须自己处理数据传输。 数据包以2 Kb MTU发送,无需重新编码,应用常规NSStream。 据Apple称,数据速率高达394 Kbps。
假设您以正常特征的形式将服务的任何数据从外围设备传输到中心。 我花了时间开通了渠道。 您在Peripheral上打开它,得到的是PSM-这是您可以连接的通道号,您需要使用相同的特性将其传输到Central。 该数字是动态的,系统本身会选择当前打开哪个PSM。 传输后,您已经可以连接到Central上的Peripheral并以您方便的格式交换数据。 让我们看看如何做到这一点。
首先,在外围设备上打开具有加密功能的端口。 您可以不进行加密而完成此操作,这样可以稍微加快传输速度。

接下来,在委托方法中,我们获取PSM并将其发送到另一个设备。

连接另一台设备后,我们将被称为一种方法,通过该方法,我们可以获取从通道进行传输所需的NSStream。

Central更加容易,我们只需连接到具有所需号码的频道即可...

...然后,我们得到了我们需要的流。 在其中,您可以传输任何大小的任何数据,并在L2CAP之上构建协议。 因此我们实现了收件人照片的传输。

但是有陷阱,没有陷阱。
陷阱
让我们看看在后台工作时的陷阱。 由于您可以使用外围设备和中央设备的角色,因此您可能会想。 您可以在后台确定哪些设备在后台附近以及哪些设备处于活动状态。 从理论上讲应该是这样,但是Apple引入了一个限制:处于后台的电话(无论是Central还是Peripheral)无法用于也处于后台的其他电话。 另外,在非iOS设备上看不到处于后台的电话。 让我们看看为什么会这样。
设备处于活动状态时,它将发送常规广播数据包,其中可能包含设备名称和服务列表。 该设备提供的。 溢出数据是所有不合适的数据。

当设备进入后台时,它不会传输名称,而是将支持的服务列表传输到溢出数据。 如果应用程序处于活动状态,则从iOS设备进行扫描时,它将读取此数据,而切换到后台时,它将忽略它。 因此,当切换到后台时,您将无法看到也在后台的应用程序。
其他Apple操作系统始终会忽略溢出数据,因此,如果您寻找支持服务的设备,则会得到一个空数组。并且,如果您连接到附近的每个设备并请求支持的服务,则列表可能包含您的服务,您可以使用它。
然后,我们准备提交测试,更正了一些小错误,并进行了优化。突然,在某个时候,我们开始在控制台中收到此错误:CoreBluetooth[WARNING] Unknown error: 124
最糟糕的是没有调用委托方法,我们甚至无法为用户克服这个错误。 日志中只有一条消息-沉默,一切都冻结了。 没有进行重大更改,因此我们开始回滚提交。 他们发现,他们曾经优化了代码并重新设计了写入数据的方式。 问题是并非所有客户端都已更新,因此发生此错误。
.write != .writeWithoutResponse
我们很高兴我们修复了所有东西,然后跑去将其传递给测试,他们几乎立即返回给我们:“您的时尚照片不起作用。 他们都来不及了。” 我们开始尝试,确实有时候在不同的设备上,破损的照片会在不同的时间出现。 他们开始寻找原因。
然后他们再次看到了先前的错误。 立刻以为它是不同的版本。 但是,在从所有测试设备中完全删除旧版本之后,该错误仍然再现。 我们很伤心...
CoreBluetooth[WARNING] Unknown error: 722 CoreBluetooth[WARNING] Unknown error: 249 CoreBluetooth[WARNING] Unknown error: 312
我们开始寻找调试工具。 我们遇到的第一件事是Apple Bluetooth Explorer。 一个功能强大的程序可以执行许多操作,但是要调试Bluetooth LE协议,可以使用一个小选项卡搜索设备并获取特性。 我们需要分析L2CAP。
然后他们找到了LightBlue Explorer。 尽管它采用的是iOS 7的设计,但事实证明这是一个相当不错的程序。它可以做与Bluetooth Explorer相同的事情,并且知道如何订阅规范。 而且工作更稳定。 一切都很好,但是同样没有L2CAP。
然后我们想起了著名的WireShark嗅探器。
原来,他对Bluetooth LE很熟悉:他可以阅读L2CAP,但只能在Windows下阅读。 虽然我们不会找到Windows之类的东西并不令人害怕。 最大的缺点-该程序仅适用于特定设备。 也就是说,您必须在官方商店的某个地方找到该设备。 您自己也了解到,大公司不太可能会在跳蚤市场上批准购买难以理解的设备。 我们甚至开始浏览海外在线商店。
但是在这里,他们在Additional Xcode Tools中找到了PacketLogger程序。 它使您可以查看OS X设备上的流量。 为什么不在OS X下重写我们的MoneyDrop? 我们已经有一个单独的库。 我们只是用NSImage替换了UIImage,一切都在10分钟后启动。

最后,我们可以读取设备之间交换的数据包。 立刻清楚的是,在通过L2CAP进行数据传输时,记录了其中一个特性。 而且由于该频道已完全被照片传输占用,因此iOS忽略了录制,忽略后的发送者中断了该频道。 修复问题后与照片的转移是没有的。

就这样,谢谢您的阅读:)
有用的链接
WWDC / CoreBluetooth:蓝牙功能YouTube的