2020 / 10 / 15
基于 iOS CoreBluetooth 框架的蓝牙开发实践,从 BLE 基础到实际开发经验分享。
因为一点机缘巧合,看了一下 iOS CoreBluetooth 文档 ,做了一个和 蓝牙交互传数据的 App
网上关于 iOS Swift 蓝牙开发的资料很少,遇到问题一度非常苦恼,好在最后把一点东西最基本的啃过来了,这里结合自己的一丁点儿开发经验方便以后查阅,也希望能给看到这里的朋友一点帮助。
低功耗蓝牙 Bluetooth Low Energy (BLE) 指蓝牙 4.0,特色是 小数据、低功耗,而在蓝牙4.0 以前是经典蓝牙,iOS Core Bluetooth 基于 Bluetooth 4.0 协议 ,兼容 iPhone 4S+ 的机器。
一些 iOS 蓝牙开发基本概念:
Charateristics 的功能一般由以下三种能力组合而成:
一图胜千言,用 Bluetility
这个工具能很好的帮助理解蓝牙的基本概念。
中心设备 C: Central, 外围设备 P: Peripheral
蓝牙连接的主要代码逻辑如下:
2019 WWDC 上放出了一个经典蓝牙开发的 Demo ,对着代码学习就完事了
新建工程
添加 Info.plist 确保用户能授权蓝牙权限
iOS 12: NSBluetoothPeripheralUsageDescription
iOS 13+: NSBluetoothAlwaysUsageDescription
编写代理方法:实现 CBCentralManagerDelegate
、CBPeripheralDelegate
方法
个人在写项目过程中遇到过三个问题:数据分包收发问题、编码解码问题、显示数据换行符的问题
在本项目中,App 和蓝牙交互的是文本字符串。可以确定的是,我们收发的文本数据发送之前都会被序列化(也可理解为编码)为二进制字节流,真正传输的数据都是字节流。而把一段文本序列化编码为怎样的二进制的字节流就有讲究了。
从 '字符串' 到 二进制,前人已经给出了多种编码方式:从最开始的 ASCII,到 GBK,再到后来的 Unicode,都是编码解码的解决方案,每一种编码都对应了一定的规则,可能是变长编码、可能编码码元不一致等。所以,需要注意的是,什么编码就用什么解码。否则就会出现字符集不一致导致的乱码问题。
当然,所以出于验证的角度,万物皆可 ASCII 解码(后来的编码都兼容 ASCII,其中的英文和字母一定可以被正常地解码,作为验证方案,已经很足够了)
在本项目中,硬件部分用 U 盘存储了 GBK 编码字符的 CSV 文件(其实主要是为了能在默认编码为 GBK 的垃圾串口调试程序 XCOM 上能看到正确的字符串),因此在 iOS App 这边就需要用 GBK 来解码来反序列化解码,而 iOS 这边本身没有直接提供 GBK 编码,最后通过 GB18030(GBK 的父集) 来解决了 GBK 解码的问题。
let GBK_ENC_RAWVALUE = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)) let UTF8_ENC_RAWVALUE = String.Encoding.utf8.rawValue let USING_ENC = GBK_ENC_RAWVALUE if let tmpString = String(data: actualData, encoding: String.Encoding(rawValue: USING_ENC)) { print("This is str encoded with GBK \(tmpString)") }
因为蓝牙的低功耗的特色,蓝牙收发数据是有长度限制的。大数据需要被拆分为小数据,才能保证数据能被正常完整地接受。其实下面的解决思路有点处理 TCP 上层应用层的拆包、粘包问题的那味道了。
假设 C 是中心设备,P 是外部硬件蓝牙设备
C 发数据:发送数据超过了 Maxmium Transmission Unit (MTU) 是一定涉及到分包的,否则数据就收不到完整的数据,甚至直接被丢掉。一定要注意的是:
P 收数据:假设 C 这边发出的数据是直接分包发送的,那么到达的数据顺序可能会不一致,简单的拼接操作得不到准确的数据。解决思路:
在 Windows,Classic macOS,Unix 及其衍生系统中,换行符是有差别的:
OS | Terminator | Terminator ASCII Number(Hexdecimal) | 意义 |
---|---|---|---|
Unix/macOS(新版) | \n | 0x0A | Line Feed |
Classic macOS | \r | 0x0D | Carriage return |
Windows/DOS | \r\n | 0x0D 0x0A | CRLF |
注: 在 Swift String 中,默认的换行符是 newline(\n)。而要想在 Windows 的串口程序(XCOM for Windows)控制栏上显示正确地换行,需要在发送数据中每一行结尾处手动加上 \r
在 ASCII 中,回车和换行是不同的字符。0x0A 是回车,即光标移动到本行的最左面;0x0D 是换行,即光标移动到下一行。
关于换行和回车,有三种不错的解释:
回车 \r :本义是光标重新回到本行开头,r 的英文 return,控制字符可以写成 CR,即 Carriage Return 换行 \n :本义是光标往下一行(不一定到下一行行首),n 的英文 newline,控制字符可以写成 LF,即 Line Feed
回车,横向操作 carriage return CR,这个名字可能是指打印头像运作起来像奔跑的马车 换行,纵向操作 line feed LF, 被吃掉一行
回车中的 "车" 指的是纸车,带着纸一起左右移动的模块; 当开始打第一个字之前,要把纸车拉到最右边,上紧弹簧,随着打字,弹簧把纸车拉回去; 每当打完一行后,纸车就完全收回去了,所以叫回车。 换行的概念就是:打字机左边有个 "把手", 往下 扳动一下,纸会上移一行
以下文章或工具对理解蓝牙的基本知识和 模拟蓝牙调试 非常有用
工具:
BLE:
换行符: