CoreBluetooth蓝牙开发 Swift版
之前写了iOS蓝牙开发OC的文章,本文是Swift的实现方式。除了语法,其实并没有太多的变化
如果想查看蓝牙相关的基本入门介绍,可到这里查看。
GitHub Demo
蓝牙外设与中心设备之间的数据传输蓝牙外设
1、首先导入CoreBluetooth框架,并另外开一个extension遵守协议
import CoreBluetooth
// 遵守CBPeripheralManagerDelegate协议
extension ViewController: CBPeripheralManagerDelegate
2、创建外设管理对象,用一个属性来强引用这个对象。并且在创建的时候设置代理,声明放到哪个线程。
private var peripheralManager: CBPeripheralManager?
private var characteristic: CBMutableCharacteristic?
// 创建外设管理器,会回调peripheralManagerDidUpdateState方法
override func viewDidLoad() {
super.viewDidLoad()
peripheralManager = CBPeripheralManager.init(delegate: self, queue: .main)
}
3、当创建CBPeripheralManager的时候,会回调判断蓝牙状态的方法。当蓝牙状态没问题的时候开始广播,并调用创建服务和特征的方法。这个方法写在extension中
// 蓝牙状态
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .unknown:
print("未知的")
case .resetting:
print("重置中")
case .unsupported:
print("不支持")
case .unauthorized:
print("未验证")
case .poweredOff:
print("未启动")
case .poweredOn:
print("可用")
// 创建Service(服务)和Characteristics(特征)
setupServiceAndCharacteristics()
// 根据服务的UUID开始广播
self.peripheralManager?.startAdvertising([CBAdvertisementDataServiceUUIDsKey : [CBUUID.init(string: Service_UUID)]])
}
}
定义两个标识字符串,用来创建服务和特征的UUID。
最终把创建好的特征放进服务,把服务放入中心管理器。
需要注意的是:swift中枚举的按位运算 '|' 要用[.read, .write, .notify]这种形式
private let Service_UUID: String = "CDD1"
private let Characteristic_UUID: String = "CDD2"
/** 创建服务和特征
注意:swift中枚举的按位运算 '|' 要用[.read, .write, .notify]这种形式
*/
private func setupServiceAndCharacteristics() {
let serviceID = CBUUID.init(string: Service_UUID)
let service = CBMutableService.init(type: serviceID, primary: true)
let characteristicID = CBUUID.init(string: Characteristic_UUID)
let characteristic = CBMutableCharacteristic.init(type: characteristicID,
properties: [.read, .write, .notify],
value: nil,
permissions: [.readable, .writeable])
service.characteristics = [characteristic]
self.peripheralManager?.add(service)
self.characteristic = characteristic
}
.Notify这个参数,只有设置了这个参数,在中心设备中才能订阅这个特征。
一般开发中可以设置两个特征,一个用来发送数据,一个用来接收中心设备写过来的数据,我们这里为了方便就只设置了一个特征。
最后用一个属性拿到这个特征,是为了后面单独发送数据的时候使用,数据的写入和读取最终还是要通过特征来完成。
4、当中心设备读取这个外设的数据的时候会回调这个方法。
/** 中心设备读取数据的时候回调 */
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
// 请求中的数据,这里把文本框中的数据发给中心设备
request.value = self.textField.text?.data(using: .utf8)
// 成功响应请求
peripheral.respond(to: request, withResult: .success)
}
5、当中心设备写入数据的时候,外设会调用下面这个方法。
/** 中心设备写入数据 */
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
let request = requests.last!
self.textField.text = String.init(data: request.value!, encoding: String.Encoding.utf8)
}
6、还有一个主动给中心设备发送数据的方法。
/** 通过固定的特征发送数据到中心设备 */
@IBAction func didClickPost(_ sender: Any) {
peripheralManager?.updateValue((textField.text ?? "empty data!").data(using: .utf8)!, for: characteristic!, onSubscribedCentrals: nil)
}
7、中心设备订阅成功的时候回调。
/** 订阅成功回调 */
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print("\(#function) 订阅成功回调")
}
8、中心设备取消订阅的时候回调。
/** 取消订阅回调 */
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
print("\(#function) 取消订阅回调")
}
下面进入iOS蓝牙开发的主要部分,中心设备的实现,这也是手机App通常担任的角色。
蓝牙中心设备
1、同外设开发一样,首先要导入CoreBluetooth框架。
import CoreBluetooth
2、遵守的协议与外设开发不同,中心设备的开发需要遵循如下两个协议。
extension ViewController: CBCentralManagerDelegate, CBPeripheralDelegate
3、创建中心管理器并用属性强引用,创建的时候也会设置代理和选择线程。
private var centralManager: CBCentralManager?
private var peripheral: CBPeripheral?
private var characteristic: CBCharacteristic?
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager.init(delegate: self, queue: .main)
}
4、当创建中心管理对象的时候,会回调如下方法用来判断中心设备的蓝牙状态。当蓝牙状态没问题的时候,可以根据外设服务的UUID来扫描需要的外设。所以自然就想到了要定义与外设UUID相同的字符串。
private let Service_UUID: String = "CDD1"
private let Characteristic_UUID: String = "CDD2"
// 判断手机蓝牙状态
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("未知的")
case .resetting:
print("重置中")
case .unsupported:
print("不支持")
case .unauthorized:
print("未验证")
case .poweredOff:
print("未启动")
case .poweredOn:
print("可用")
central.scanForPeripherals(withServices: [CBUUID.init(string: Service_UUID)], options: nil)
}
}
5、当扫描到外设之后,就会回调下面这个方法,可以在这个方法中继续设置筛选条件,例如根据外设名字的前缀来选择,如果符合条件就进行连接。
/** 发现符合要求的外设 */
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
self.peripheral = peripheral
// 根据外设名称来过滤
// if (peripheral.name?.hasPrefix("WH"))! {
// central.connect(peripheral, options: nil)
// }
central.connect(peripheral, options: nil)
}
7、当连接成功的时候,就会来到下面这个方法。为了省电,当连接上外设之后,就让中心设备停止扫描,并且别忘记设置连接上的外设的代理。在这个方法里根据UUID进行服务的查找。
/** 连接成功 */
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
self.centralManager?.stopScan()
peripheral.delegate = self
peripheral.discoverServices([CBUUID.init(string: Service_UUID)])
print("连接成功")
}
8、连接失败和断开连接也有各自的回调方法。在断开连接的时候,我们可以设置自动重连,根据项目需求来自定义里面的代码。
/** 连接失败的回调 */
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("连接失败")
}
/** 断开连接 */
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("断开连接")
// 重新连接
central.connect(peripheral, options: nil)
}
9、下面开始处理代理方法。
最开始就是发现服务的方法。这个方法里可以遍历服务,找到需要的服务。由于上面做的外设只有一个服务,所以我这里直接取服务中的最后一个lastObject就行了。
找到服务之后,连贯的动作继续根据特征的UUID寻找服务中的特征。
/** 发现服务 */
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service: CBService in peripheral.services! {
print("外设中的服务有:\(service)")
}
//本例的外设中只有一个服务
let service = peripheral.services?.last
// 根据UUID寻找服务中的特征
peripheral.discoverCharacteristics([CBUUID.init(string: Characteristic_UUID)], for: service!)
}
10、下面这个方法里做的事情不少。
当发现特征之后,与服务一样可以遍历特征,根据外设开发人员给的文档找出不同特征,做出相应的操作。
我的外设只设置了一个特征,所以也是直接通过lastObject拿到特征。
再重复一遍,一般开发中可以设置两个特征,一个用来发送数据,一个用来接收中心设备写过来的数据。
这里用一个属性引用特征,是为了后面通过这个特征向外设写入数据或发送指令。
readValueForCharacteristic方法是直接读一次这个特征上的数据。
/** 发现特征 */
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic: CBCharacteristic in service.characteristics! {
print("外设中的特征有:\(characteristic)")
}
self.characteristic = service.characteristics?.last
// 读取特征里的数据
peripheral.readValue(for: self.characteristic!)
// 订阅
peripheral.setNotifyValue(true, for: self.characteristic!)
}
setNotifyValue()方法是对这个特征进行订阅,订阅成功之后,就可以监控外设中这个特征值得变化了。
11、当订阅的状态发生改变的时候,下面的方法就派上用场了。
/** 订阅状态 */
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("订阅失败: \(error)")
return
}
if characteristic.isNotifying {
print("订阅成功")
} else {
print("取消订阅")
}
}
12、外设可以发送数据给中心设备,中心设备也可以从外设读取数据,当发生这些事情的时候,就会回调这个方法。通过特种中的value属性拿到原始数据,然后根据需求解析数据。
/** 接收到数据 */
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let data = characteristic.value
self.textField.text = String.init(data: data!, encoding: String.Encoding.utf8)
}
13、中心设备可以向外设写入数据,也可以向外设发送请求或指令,当需要进行这些操作的时候该怎么办呢。
-
首先把要写入的数据转化为NSData格式,然后根据上面拿到的写入数据的特征,运用方法open func writeValue(_ data: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType)来进行数据的写入。
-
当写入数据的时候,系统也会回调这个方法func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) 。
@IBAction func didClickPost(_ sender: Any) {
let data = (self.textField.text ?? "empty input")!.data(using: String.Encoding.utf8)
self.peripheral?.writeValue(data!, for: self.characteristic!, type: CBCharacteristicWriteType.withResponse)
}
/** 写入数据 */
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("写入数据")
}
14、中心设备如何主动从外设读取数据呢。
- 用正在连接的外设对象来调用readValue(for characteristic: CBCharacteristic)方法,并且把将要读取数据的特征作为参数,这样就可以主动拿一次数据了。
去到第12步的回调方法中,在特征的value属性中拿到这次的数据。
@IBAction func didClickGet(_ sender: Any) {
self.peripheral?.readValue(for: self.characteristic!)
}
后记
中心设备的开发是需要配合外设来进行的,一般会有硬件工程师或嵌入式工程师给出通信协议,根据协议来对项目的各种需求进行操作。
本文所述的示例代码在这里:Demo
推荐简单又好用的分类集合:WHKit