IOS蓝牙的相关操作
在公司项目里面有一个子系统的功能是使用手机向蓝牙外设发送数据,蓝牙外设会向手机汇报当前的状态,也就是要实现一个蓝牙外设和手机的双向数据交互。
在IOS中,蓝牙的相关操作主要有以下几个专有名词需要熟悉
- CentralManager: 管理中心,负责蓝牙外设的搜索和连接
- CBPeripheral: 蓝牙外设对象,包含了外设的名称,信号强度,所属服务等信息
- Peripheral-Service: 蓝牙外设的服务,一个蓝牙外设可以拥有许多服务,可以在服务中完成相应的读写操作
- Service-Characteristic: 蓝牙外设服务的特征,蓝牙外设和管理中心的数据读写需要借助特定的特征,类似于通道的功能
如果要在程序中进行开发的话,需要先熟悉一下蓝牙的相关操作流程
- 管理中心发送广播,搜索当前范围内的蓝牙外设
- 管理中心连接指定的蓝牙外设
- 管理中心发现外设服务
- 管理中心发现服务的特征
- 通过外设的服务特征将数据写入外设
- 管理中心断开连接
准备好了这些概念,就可以进行蓝牙外设的开发了,首先需要在info.plist文件中编写相关的说明文字,以此来通过IOS的权限检查,分别设置Privacy - Bluetooth Always Usage Description,Privacy - Bluetooth Peripheral Usage Description
之后需要进行代码的编写了,首先创建一个蓝牙工具类的文件,然后在其中编写相关的属性备用,之后再编写相关的蓝牙管理中心的回调函数,方便我们在发现蓝牙外设和连接外设时进行相关的操作
import UIKit
import CoreBluetooth
class LMBlueToothUtils: NSObject {
static let shared = LMBlueToothUtils()
var centralManager : CBCentralManager!
//已连接的蓝牙外设
var connectPeripheral : CBPeripheral? = nil
//搜索到的附近的外设
var peripherals : [CBPeripheral] = [CBPeripheral]()
//要使用的蓝牙外设的特征
var writeCharacteristic : CBCharacteristic!
//发现蓝牙外设的闭包
var blueToothPeripheralClosures : (([CBPeripheral]) -> ())!
//分包数据是否继续发送的闭包
var bigDataSendStatuClosures : ((Bool) -> ())!
//蓝牙连接成功的回调
var blueToothConnectStatuClosures : ((Bool) -> ())!
}
extension LMBlueToothUtils : CBCentralManagerDelegate {
//管理中心蓝牙状态的更新
func centralManagerDidUpdateState(_ central: CBCentralManager) {
}
//中心管理者发现外设
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
}
//中心管理者连接外设成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
}
//中心管理者连接外设失败
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
}
}
首先编写检查蓝牙状态的函数,主要是检查蓝牙是否处于开启状态和本应用是否获取到了系统蓝牙的授权,可以使用AlertController提示用户进行相关的操作
/// 检查蓝牙开启状态,检查蓝牙授权情况
func checkBleStatu() -> Bool {
if centralManager == nil {
centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main, options: nil)
}
if centralManager.state == .poweredOff || centralManager.state == .unauthorized {
connectPeripheral = nil
peripherals = [CBPeripheral]()
writeCharacteristic = nil
print("蓝牙未开启 | 应用蓝牙未授权")
return false
}else {
print("蓝牙状态可用")
return true
}
}
之后编写中心管理者发现蓝牙外设的处理函数,centralManager有一个scanForPeripherals的方法,可以调用它,使得手机蓝牙作为主设备进行广播,搜索范围内的蓝牙外设,搜索到外设以后,会在对应的delegate中得到搜索到的外设信息,包括外设的名称。外设的信号强度等信息,可以做出相应的处理,也可以使用事先写好的闭包进行数据的返回和处理
/// 扫描附近的蓝牙外设
func discoverNearbyPeripherals() {
if centralManager == nil {
centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main, options: nil)
}
//开始扫描
centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
}
/// 重新扫描外设
func reDiscoverNearbyPeripherals() {
peripherals.removeAll()
centralManager.stopScan()
if blueToothPeripheralClosures != nil {
blueToothPeripheralClosures(peripherals)
}
discoverNearbyPeripherals()
}
/// 设置获取附近蓝牙外设的闭包
func setBlueToothPeripheralClosures(_ closure : @escaping (([CBPeripheral]) -> ())) {
blueToothPeripheralClosures = closure
}
/// delegate
//中心管理者发现外设
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//过滤掉信号太弱的蓝牙外设
if RSSI.intValue > -100 && RSSI.intValue < 0 {
//过滤到蓝牙名称不符合规定的蓝牙外设
if let pname = peripheral.name, pname.hasPrefix("APEX-"), !peripherals.contains(peripheral){
//添加外设到外设数组
peripherals.append(peripheral)
//闭包返回
if blueToothPeripheralClosures != nil {
blueToothPeripheralClosures(peripherals)
}
}
}
}
之后再去编写蓝牙外设的连接函数,同样可以使用centralManager的connect方法,不过要将要连接的外设作为参数传进去,之后无论连接成功还是连接失败,都会触发delegate的对应方法,可以在里面编写相应的代码,做出合理的处理
/// 连接蓝牙外设
/// - Parameter peripheral: 要连接的蓝牙外设
func connectPeripheral(peripheral : CBPeripheral) {
if centralManager == nil {
centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main, options: nil)
}
//取消蓝牙扫描
centralManager.stopScan()
//取消上一次蓝牙连接
if let lastPeripheral = connectPeripheral {
centralManager.cancelPeripheralConnection(lastPeripheral)
}
//连接蓝牙外设
connectPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
/// 设置获取蓝牙连接状态的闭包
func setBlueToothConnectStatuClosures(_ closure : @escaping ((Bool) -> ())) {
blueToothConnectStatuClosures = closure
}
/// delegate
//中心管理者连接外设成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
//设置蓝牙外设的回调
peripheral.delegate = self
//蓝牙外设发现对应服务,如果需要进行相关的uuid筛选,可以将其作为参数传进去
peripheral.discoverServices(nil)
//重新扫描蓝牙外设
reDiscoverNearbyPeripherals()
if blueToothPeripheralClosures != nil {
blueToothPeripheralClosures(peripherals)
}
if blueToothConnectStatuClosures != nil {
blueToothConnectStatuClosures(true)
}
}
//中心管理者连接外设失败
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("蓝牙外设连接失败")
if blueToothConnectStatuClosures != nil {
blueToothConnectStatuClosures(false)
}
}
如果这样写的话,应该会报错的,因为连接成功以后,需要设置相关的外设delegate回调,我们再写一个extension
extension LMBlueToothUtils : CBPeripheralDelegate {
//发现蓝牙外设的服务
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
}
//发现服务对应的特征
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
}
//获取到蓝牙外设传递过来的信息
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let data = characteristic.value else {
return
}
parseCharacteristicValue(data: data)
}
//将信息写入到蓝牙外设时的回调
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
}
//处理蓝牙外设发送的信息的处理函数
func parseCharacteristicValue(data : Data) {
}
}
蓝牙外设连接成功以后,可以编写函数,使得外设开始搜索外设的服务,这时候如果外设服务搜索到的话,会在CBPeripheralDelegate的相关函数中得到相关的服务信息,不同服务之间可以使用服务的uuid进行筛选和区分,在这里,接纳所有的服务
//发现蓝牙外设的服务
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
for service in services {
//可以在这里设置uuid过滤得到特定的服务
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
对于服务而言,我们可以对于每个服务,搜索其拥有的服务特征,如果搜索到对应的特征,也可以在delegate中的函数里做出相应的处理
//发现服务对应的特征
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
let properties = characteristic.properties
//设置notify特征
if (properties.rawValue & CBCharacteristicProperties.notify.rawValue) != 0 {
peripheral.setNotifyValue(true, for: characteristic)
}
//设置向外设写数据的特征
if (properties.rawValue & CBCharacteristicProperties.write.rawValue) != 0 {
writeCharacteristic = characteristic
}
}
}
}
对于特征而言,可以简单分为以下三种
- write
- read
- notify
先找到可以用于向外设写入数据的特征,方便后面进行数据的发送,找到用于notify的特征,方便后面接收到外设发送的状态信息。
确定了特征以后,再向蓝牙外设写入数据就方便了很多,可以直接使用writevalue方法,相关的断开连接的方法,这里就不一个一个写出来了,完成的代码,贴在最后
//
// LMBlueToothUtils.swift
// LMBleUtils
//
// Created by IT-GO-0367 on 2021/7/13.
//
import UIKit
import CoreBluetooth
class LMBlueToothUtils: NSObject {
static let shared = LMBlueToothUtils()
var centralManager : CBCentralManager!
//已连接的蓝牙外设
var connectPeripheral : CBPeripheral? = nil
//搜索到的附近的外设
var peripherals : [CBPeripheral] = [CBPeripheral]()
//要使用的蓝牙外设的特征
var writeCharacteristic : CBCharacteristic!
//发现蓝牙外设的闭包
var blueToothPeripheralClosures : (([CBPeripheral]) -> ())!
//分包数据是否继续发送的闭包
var bigDataSendStatuClosures : ((Bool) -> ())!
//蓝牙连接成功的回调
var blueToothConnectStatuClosures : ((Bool) -> ())!
/// 检查蓝牙开启状态,检查蓝牙授权情况
func checkBleStatu() -> Bool {
if centralManager == nil {
centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main, options: nil)
}
if centralManager.state == .poweredOff || centralManager.state == .unauthorized {
connectPeripheral = nil
peripherals = [CBPeripheral]()
writeCharacteristic = nil
print("蓝牙未开启 | 应用蓝牙未授权")
return false
}else {
print("蓝牙状态可用")
return true
}
}
/// 扫描附近的蓝牙外设
func discoverNearbyPeripherals() {
if centralManager == nil {
centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main, options: nil)
}
//开始扫描
centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
}
/// 重新扫描外设
func reDiscoverNearbyPeripherals() {
peripherals.removeAll()
centralManager.stopScan()
if blueToothPeripheralClosures != nil {
blueToothPeripheralClosures(peripherals)
}
discoverNearbyPeripherals()
}
/// 设置获取附近蓝牙外设的闭包
func setBlueToothPeripheralClosures(_ closure : @escaping (([CBPeripheral]) -> ())) {
blueToothPeripheralClosures = closure
}
/// 连接蓝牙外设
/// - Parameter peripheral: 要连接的蓝牙外设
func connectPeripheral(peripheral : CBPeripheral) {
if centralManager == nil {
centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main, options: nil)
}
//取消蓝牙扫描
centralManager.stopScan()
//取消上一次蓝牙连接
if let lastPeripheral = connectPeripheral {
centralManager.cancelPeripheralConnection(lastPeripheral)
}
//连接蓝牙外设
connectPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
/// 设置获取蓝牙连接状态的闭包
func setBlueToothConnectStatuClosures(_ closure : @escaping ((Bool) -> ())) {
blueToothConnectStatuClosures = closure
}
func writeDataToPeripheral(data : Data) {
//准备写入数据
if let peripheral = connectPeripheral, let characteristic = writeCharacteristic {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
}
}
}
extension LMBlueToothUtils : CBCentralManagerDelegate {
//管理中心蓝牙状态的更新
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
// 蓝牙开启-扫描外设
centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
}
else {
print("蓝牙未开启");
}
}
//中心管理者发现外设
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//过滤掉信号太弱的蓝牙外设
if RSSI.intValue > -100 && RSSI.intValue < 0 {
//过滤到蓝牙名称不符合规定的蓝牙外设
if let pname = peripheral.name, pname.hasPrefix("APEX-"), !peripherals.contains(peripheral){
//添加外设到外设数组
peripherals.append(peripheral)
//闭包返回
if blueToothPeripheralClosures != nil {
blueToothPeripheralClosures(peripherals)
}
}
}
}
//中心管理者连接外设成功
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
//设置蓝牙外设的回调
peripheral.delegate = self
//蓝牙外设发现对应服务,如果需要进行相关的uuid筛选,可以将其作为参数传进去
peripheral.discoverServices(nil)
//重新扫描蓝牙外设
reDiscoverNearbyPeripherals()
if blueToothPeripheralClosures != nil {
blueToothPeripheralClosures(peripherals)
}
if blueToothConnectStatuClosures != nil {
blueToothConnectStatuClosures(true)
}
}
//中心管理者连接外设失败
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("蓝牙外设连接失败")
if blueToothConnectStatuClosures != nil {
blueToothConnectStatuClosures(false)
}
}
}
extension LMBlueToothUtils : CBPeripheralDelegate {
//发现蓝牙外设的服务
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
for service in services {
//可以在这里设置uuid过滤得到特定的服务
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
//发现服务对应的特征
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
let properties = characteristic.properties
//设置notify特征
if (properties.rawValue & CBCharacteristicProperties.notify.rawValue) != 0 {
peripheral.setNotifyValue(true, for: characteristic)
}
//设置向外设写数据的特征
if (properties.rawValue & CBCharacteristicProperties.write.rawValue) != 0 {
writeCharacteristic = characteristic
}
}
}
}
//获取到蓝牙外设传递过来的信息
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard let data = characteristic.value else {
return
}
parseCharacteristicValue(data: data)
}
//将信息写入到蓝牙外设时的回调
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
}
//处理蓝牙外设发送的信息的处理函数
func parseCharacteristicValue(data : Data) {
if data[0] == 0x50 {
print("缺纸")
}
}
}