承接上篇swift3.0蓝牙开发(1)
三.代码展示
1.设置代理
CBCentralManagerDelegate 中心者的代理
CBPeripheralDelegate 外设的代理
class ViewController: UIViewController,CBCentralManagerDelegate,CBPeripheralDelegate
2.定义全局的中心者对象
/// 中心者对象
var central: CBCentralManager
/// 外设数组
var peripheralArray = NSMutableArray.init()
3.初始化中心者对象(第一个参数是设置代理,第二个参数是队列,这不讲,涉及多线程开发了)
/// 初始化中心设备
func initBluetooth() {
//MARK: -1.初始化本地中心设备对象
central = CBCentralManager.init(delegate: self, queue: nil)
}
4.当初始化完中心者对象后,就会回调以下的方法,就是当初始化中心者对象那句代码运行过完后,就会走到以下的方法
这个方法是用来检查手机(中心者)的蓝牙状态,比如是否打开啊,是否支持蓝牙4.0啊
func centralManagerDidUpdateState(_ central: CBCentralManager) {
self.writeToTextView(string: "初始化对象后,来到centralManagerDidUpdateState")
switch central.state {
case .unknown:
print("CBCentralManager state:", "unknown")
break
case .resetting:
print("CBCentralManager state:", "resetting")
break
case .unsupported:
print("CBCentralManager state:", "unsupported")
break
case .unauthorized:
print("CBCentralManager state:", "unauthorized")
break
case .poweredOff:
print("CBCentralManager state:", "poweredOff")
break
case .poweredOn:
print("CBCentralManager state:", "poweredOn")
//MARK: -3.扫描周围外设(支持蓝牙)
// 第一个参数,传外设uuid,传nil,代表扫描所有外设
central.scanForPeripherals(withServices: nil, options: nil)
// 每次搜索到一个外设都会回调起代理方法centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
break
}
5.在以上方法的蓝牙打开的控制流分支中,扫描周围的外设,当成功扫描到外设的时候,就会来到以下的方法
以下方法参数:
/// - central: 中心设备实例对象
/// - peripheral: 外设实例对象
/// - advertisementData: 一个包含任何广播和扫描响应数据的字典
/// - RSSI: 外设的蓝牙信息强度
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// 添加外设到外设数组(如果不保存这个外设或者说没有对这个外设对象有个强引用的话,就没有办法到达连接成功的方法,因此没有强引用的话,这个方法过后,就销毁外设对象了)
// 连接外设成功或者失败分别会进入回调方法
// MAKE: -连接外设
peripheralArray.add(peripheral)
// 或者,你可以建立一个全局的外设对象。例如
// self.peripheral = peripheral 这就是强引用这个局部的外设对象,这样就不会导致出了这个方法这个外设对象就被销毁了
//MARK: -5.连接外设
// 添加完后,就开始连接外设
central.connect(peripheral, options: nil) // 会根据条件触发,连接成功,失败,断开连接的代理方法
// 如果你扫描到多个外设,要连接特定的外设,可以用以下方法
// if peripheral.name == "A" {
// // 连接设备
// central.connect(peripheral, options: nil)
// }
}
6.当连接外设成功后,就会来到外设连接成功的回调方法,失败也有失败的回调方法,分别如下
// 连接成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// 连接成绩后,接下来就要开始对外设进行寻找服务 特征 读写,所以要开始用到外设的代理方法,所以这里要设置外设的代理为当前控制器
peripheral.delegate = self
// 设置完后,就开始扫描外设的服务
// 参数设了nil,是扫描所有服务,如果你知道你想要的服务的UUID,那么也可以填写指定的UUID
peripheral.discoverServices(nil) //这里会回调代理peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) 这里方法里有这个外设的服务信息
}
// 连接失败
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
}
7.在上面连接成功的回调方法里,扫描外设的服务,当扫描成功后,就会来到发现外设服务的方法,如下
// 外设的服务信息
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)// 发现特征,第二个参数填哪个服务的特征,这个方法会回调特征信息的方法
print("服务UUID:\(service.uuid.uuidString)")
}
}
这里要注意的是
peripheral.services!是一个数组
所以不能
peripheral.services.uuid 这样是错误的
8.以上是发现服务的回调方法,在上面的方法中,可以使用发现服务下的特征的方法,为什么,我发现特征的方法一定要在这个发现服务的方法里面写,我不能直接写发现特征吗
答:你可以去其他地方写写看,peripheral.discoverCharacteristics(nil, for: service)这是发现特征的方法,这个方法的第二个参数是填写服务的,那么我们是只能在这个发现服务的回调方法里才可以拿到这个服务的对象,所以就只能在这个方法服务的方法里写了
当运行完发现特征的方法后,就会来到发现特征的回调方法
// 特征信息的方法
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
print("\(service.uuid.uuidString)服务下的特性:\(characteristic)")
if characteristic.uuid == CBUUID (string: "FFF0") {
//MARK: -9.读取指定特征的信息
//读取特征数据
peripheral.readValue(for: characteristic)//读取都就会进入didUpdate的代理方法
}
if characteristic.uuid == CBUUID (string: "FFF0") {
//订阅通知
/**
-- 通知一般在蓝牙设备状态变化时会发出,比如蓝牙音箱按了上一首或者调整了音量,如果这时候对应的特征被订阅,那么app就可能收到通知
-- 阅读文档查看需要对该特征的操作
-- 订阅成功后回调didUpdateNotificationStateForCharacteristic
-- 订阅后characteristic的值发生变化会发送通知到didUpdateValueForCharacteristic
-- 取消订阅:设置setNotifyValue为NO
*/
//这里我们可以使用readValueForCharacteristic:来读取数据。如果数据是不断更新的,则可以使用setNotifyValue:forCharacteristic:来实现只要有新数据,就获取。
peripheral.setNotifyValue(true, for: characteristic)
}
//扫描描述
/**
-- 进一步提供characteristic的值的相关信息。(因为我项目里没有的特征没有进一步描述,所以我也不怎么理解)
-- 当发现characteristic有descriptor,回调didDiscoverDescriptorsForCharacteristic
*/
peripheral.discoverDescriptors(for: characteristic)
}
}
上面方法有大家应该会有点疑惑的,就是什么时候才调用didUpdateNotificationStateForCharacteristic方法,这个方法有什么用,这个方法会获取到外设发送到手机的数据,还有什么的订阅特征
答:didUpdateNotificationStateForCharacteristic这个方法有两种情况会调用:
(1).订阅特征的时候 peripheral.setNotifyValue(true, for: characteristic)
(2).读取数据的时候peripheral.readValue(for: characteristic)
什么是订阅特征?
答:比如说现在我的外设是一个蓝牙音箱,里面有一个特征是下一首歌的特征,那么对于这种特征,我们就要采用订阅特征的方法,这个订阅有什么用呢,如果我订阅了这个特征,那么这个特征一更新数据,我就可以在didUpdateNotificationStateForCharacteristic方法中获取到数据
下一首歌开发思路
1.我定于下一首歌的特征
2.用户按了蓝牙音箱下一首歌
3.因为我订阅了这个特征,所以我可以在didUpdateNotificationStateForCharacteristic方法中拿到用户按了下一首歌这个按钮的信息
4.调用程序下一首歌的方法,执行
5.在didUpdateNotificationStateForCharacteristic方法中给蓝牙写数据,因为要快啊,我在这里拿到数据下一首歌,我应该马上响应切换下一首歌,并且给蓝牙的另一个特征写数据,告诉他,我切歌了
9.didUpdateNotificationStateForCharacteristic方法
/**
-- peripheral调用readValueForCharacteristic成功后会回调该方法
-- peripheral调用setNotifyValue后,特征发出通知也会调用该方法
*/
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// 写数据,第一个参数转data类型的数据,第二个参数传写入数据的特征,第三个参数是枚举类型分别是CBCharacteristicWriteWithResponse和 CBCharacteristicWriteWithoutResponse;
// peripheral.writeValue(<#T##data: Data##Data#>, for: <#T##CBCharacteristic#>, type: <#T##CBCharacteristicWriteType#>)
}
// 对于以上的枚举类型的第一个CBCharacteristicWriteWithResponse,每往硬件写入一次数据就会调用一下代理方法
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
}
//订阅通知的回调
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
}
#后附上讲解demo
https://github.com/ChenZeBin/CoreBluetoothDemo.git
demo中是很多注释,基本看demo也可以看懂