转载

CoreBluetooth蓝牙开发 Swift版

之前写了iOS蓝牙开发OC的文章,本文是Swift的实现方式。除了语法,其实并没有太多的变化

如果想查看蓝牙相关的基本入门介绍,可到这里查看。

GitHub Demo

CoreBluetooth蓝牙开发 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

正文到此结束
Loading...