之前写了iOS蓝牙开发OC的文章,本文是Swift的实现方式。除了语法,其实并没有太多的变化
如果想查看蓝牙相关的基本入门介绍,可到这里查看。
蓝牙外设与中心设备之间的数据传输
蓝牙外设
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) } 6 /** 写入数据 */ 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