iOS专题1-蓝牙扫描、连接、读写

概念

外围设备

可以被其他蓝牙设备连接的外部蓝牙设备,不断广播自身的蓝牙名及其数据,如小米手环、共享单车、蓝牙体重秤

中央设备

可以搜索并连接周边的外围设备,并与之进行数据读写通讯,如手机

日常生活中常见的场景是手机app通过蓝牙开启共享单车,手机app通过蓝牙获取蓝牙体重秤的体重结果,这时候共享单车、蓝牙体重秤就称为外围设备,而手机就称为中央设备

经典蓝牙BT

泛指支持蓝牙协议在4.0以下的模块,一般用于数据量比较大的传输,如:语音、音乐等较高数据量的传输。经典蓝牙模块又可细分为:传统蓝牙和高速蓝牙模块。传统蓝牙模块在2004年推出,主要代表是支持蓝牙2.1协议的模块,在智能手机爆发的时期得到了广泛的使用。高速蓝牙模块在2009年推出,速率提高到约24Mbps,传输速率是经典蓝牙的八倍,可以轻松的应用于录像机到电视、PC到PMP、UMPC到打印机之间的资料传输。

低功耗蓝牙BLE

是指支持蓝牙协议4.0或者以上的模块,也被称为BLE模块,最大的特点就是成本和功耗的降低,可以应用于实时性要求较高的产品当中,比如:智能家居类(蓝牙锁、蓝牙灯)、传感设备的数据发送(血压计、温度传感器)、消费类电子(电子烟、遥控玩具)等。

目前市面上大部分的蓝牙都是4.0以上的低功耗蓝牙,经典蓝牙已经很少见到了。

通讯过程

一个外围设备可以发布多个服务service,每个服务可以包含多个特征值characteristic,每个特征值都有他的属性,例如长度(size),权限(permission),值(value),描述(descriptor),读写通讯都是通过Characteristic进行的。
每个service、characteristic都含有一个对应的UUID,通过和外围设备蓝牙约定UUID来进行读写通讯。

整个通讯流程为:

graph TB
B[创建蓝牙实例]  --> S[搜索扫描外围设备]
S --> C[连接外围设备]
C --> SE[获取外围设备的服务service]
SE --> CH[获取服务的特征characteristic] 
CH --> R[从外围设备读取数据,即读数据]
R --> W[给外围设备发送数据写数据,即写数据]
W --> D[断开连接]

常用业务API

1.判断当前蓝牙是否已经开启,如果没有开启提示用户开启
2.实时扫描周边蓝牙,获取蓝牙名给用户选择
3.解析蓝牙广播数据处理业务
4.监听外围设备发送给app的数据,处理对应业务
5.app发送数据给外围设备以处理业务

配置Info.plist

NSBluetoothAlwaysUsageDescription
开机访问蓝牙需要您的授权
NSBluetoothPeripheralUsageDescription
开机访问蓝牙需要您的授权

完整的代码:
Bluetooth.swift

//
//  Bluetooth.swift
//  ProjectApp
//
//  Created by jack on 2021/1/11.
//

import Foundation
import CoreBluetooth

class Bluetooth: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate  {
    var centralMgr: CBCentralManager!
    var curPeripheral: CBPeripheral?
    var writeCharacteristic: CBCharacteristic?
    
    override init() {
        super.init()
        var backgroundMode: Bool = false
        if let infoDic = Bundle.main.infoDictionary {
            let tempModes = infoDic["UIBackgroundModes"]
            if let modes = tempModes as? Array {
                if modes.contains("bluetooth-central") {
                    backgroundMode = true
                }
            }
        }
        if backgroundMode {
            let options: [String : Any] = [
                CBCentralManagerOptionShowPowerAlertKey: true, // 初始化时若此时蓝牙系统为关闭状态,是否向用户显示警告对话框
                CBCentralManagerOptionRestoreIdentifierKey: "MWellnessBluetoothRestore" // 中心管理器的唯一标识符,系统根据这个标识识别特定的中心管理器,为了继续执行应用程序,标识符必须保持不变,才能还原中心管理类
            ]
            self.centralMgr = CBCentralManager(delegate: self, queue: nil, options: options)
        } else {
            self.centralMgr = CBCentralManager(delegate: self, queue: nil)
        }
    }
    
    // MARK: - CBCentralManagerDelegate
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        self.centralMgr = central
        switch central.state {
        case .poweredOn:
            DLOG(message: "蓝牙打开")
            // 蓝牙已经打开,可以开始扫描蓝牙设备
            self.scanBluetooth()
            break;
        case .poweredOff:
            DLOG(message: "蓝牙关闭")
            // 这里检测到蓝牙关闭,一般会弹框提醒用户开启蓝牙
            break
        default:
         
           break
        }

    }
    
    
    /// 开始扫描蓝牙
    /// - Returns:
    func scanBluetooth() -> Void {
        var options: [String: Any] = [String: Any]()
        options[CBCentralManagerScanOptionAllowDuplicatesKey] = NSNumber(value: false)
        var services: [CBUUID]? // 这里可以通过筛选蓝牙的广播service来筛选扫描蓝牙
        self.centralMgr.scanForPeripherals(withServices: services, options: options)
        // TODO 扫描时,一般需要开个定时器来处理扫描超时业务,自行添加
    }
    
    /// 实时扫描到蓝牙时回调
    /// - Parameters:
    ///   - central:备
    ///   - peripheral: 扫描到的蓝牙设备
    ///   - advertisementData: 广播内容
    ///   - RSSI: 蓝牙信号强度,大部分为负数,负数值越大表示信号越强
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if let name = peripheral.name {
            // 实时扫描到蓝牙后,一般有两种常用业务
            // 1.弹框列出扫描的的蓝牙让用户选择连接哪个蓝牙
            // 2.业务已经约定好蓝牙名的匹配方式,扫描到后直接连接
            
            if name.lowercased().starts(with: "SHww-") {
                // 先停止扫描,再连接
                central.stopScan()
                self.curPeripheral = peripheral
                self.centralMgr.connect(peripheral, options: nil)
            }
        }
        
        // 获取对方蓝牙广播中的厂家数据
        if let manData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data {
            
        }
        // 获取对方蓝牙广播中的蓝牙名,从peripheral.name获取的蓝牙名可能是手机缓存中的名称,从广播中获取的是实时的名称
        if let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
            
        }
        
        if let txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber {
            
        }
        if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
            
        }
        if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [String : Any] {
            
        }
       // 其他的还有CBAdvertisementDataOverflowServiceUUIDsKey,CBAdvertisementDataIsConnectable,CBAdvertisementDataSolicitedServiceUUIDsKey
        
    }
   
    
    /// 蓝牙连接失败
    /// - Parameters:
    ///   - central:
    ///   - peripheral:
    ///   - error:
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {

    }
    
    
    /// 蓝牙断开连接
    /// - Parameters:
    ///   - central:
    ///   - peripheral:
    ///   - error:
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
      
    }
    
    
    /// 蓝牙连接成功
    /// - Parameters:
    ///   - central:
    ///   - peripheral:
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        // 连接成功后,先设置蓝牙代理,再查找蓝牙对应的service
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    // MARK: - CBPeripheralDelegate
    
    /// 找到对方蓝牙service
    /// - Parameters:
    ///   - peripheral:
    ///   - error:
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        // 找到对方蓝牙服务后,筛选service再查找服务对应的Characteristic,service的UUID双方先约定要,这里以FFE0开头的就是要找的服务,实际情况根据自己业务自行处理
        if let services = peripheral.services, let per = self.curPeripheral {
            for service in services {
                if service.uuid.uuidString.starts(with: "FFE0") {
                    per.discoverCharacteristics(nil, for: service)
                }
            }
        }
    }
    
    
    /// 找到对方蓝牙的Characteristic
    /// - Parameters:
    ///   - peripheral:
    ///   - service:
    ///   - error:
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        // 找到读、写Characteristic,读、写Characteristic的UUID双方先约定要,这里以FFE0开头的就是要找的服务,以FFE1开头的就是要找的可读Characteristic,以FFE2开头的就是要找的可写Characteristic,实际情况根据自己业务自行处理
        if let services = peripheral.services {
            for service in services {
                let serviceUUID = service.uuid.uuidString
                if serviceUUID.uppercased().contains("FFE0") {
                    if let characteristics = service.characteristics {
                        for characteristic in characteristics {
                            let characteristicUUID = characteristic.uuid.uuidString
                            if characteristicUUID.uppercased().contains("FFE1") { // 找到读Characteristic,设置为可通知
                                peripheral.setNotifyValue(true, for: characteristic)
                            } else if characteristicUUID.uppercased().contains("FFE2") { // 找到写Characteristic,临时保存为变量
                                self.writeCharacteristic = characteristic
                            }
                        }
                    }
                }
            }
        }
    }
    
    
    /// 收到对方蓝牙发送的数据时回调
    /// - Parameters:
    ///   - peripheral:
    ///   - characteristic:
    ///   - error:
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
       
    }
    
    
    /// 发送数据成功时回调
    /// - Parameters:
    ///   - peripheral:
    ///   - characteristic:
    ///   - error:
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
       
    }
    
    
    /// 给对方蓝牙发送数据
    /// - Parameter data:
    /// - Returns:
    func sendData(data: [UInt8]) -> Void {
        if let per = self.curPeripheral, let ch = self.writeCharacteristic {
            let sendData: Data = Data(data)
            let properties: CBCharacteristicProperties = ch.properties
            if properties == CBCharacteristicProperties.writeWithoutResponse {
                per.writeValue(sendData, for: ch, type: CBCharacteristicWriteType.withoutResponse)
            } else {
               per.writeValue(sendData, for: ch, type: CBCharacteristicWriteType.withResponse)
            }
        }
    }
}

项目源码下载

你可能感兴趣的:(iOS专题1-蓝牙扫描、连接、读写)