美文网首页ios直播
iOS专题1-蓝牙扫描、连接、读写

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

作者: 浪人残风 | 来源:发表于2021-03-29 09:57 被阅读0次

    概念

    外围设备

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

    中央设备

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

    日常生活中常见的场景是手机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

    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>开机访问蓝牙需要您的授权</string>
    <key>NSBluetoothPeripheralUsageDescription</key>
    <string>开机访问蓝牙需要您的授权</string>
    

    完整的代码:
    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<String> {
                    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-蓝牙扫描、连接、读写

        本文链接:https://www.haomeiwen.com/subject/dewvhltx.html