swift 蓝牙集成开发

前一阶段刚刚做完了对接体脂称和智能音箱,空下来整理一下蓝牙的简单集成,不瞎叭叭了,进入正题。

一、概况

蓝牙4.0标准包括传统蓝牙部分和低功耗蓝牙模块部分

二、蓝牙模块

蓝牙模块是指集成蓝牙功能的芯片基本电路集合,用于短距离2.4G的无线通讯模块。
对于终端用户来说,蓝牙模块是半成品,通过在模块的基础上做功能二次开发、封装外壳等工序,实现能够利用蓝牙通讯的最终产品。

蓝牙模块按照应用和支持协议划分主要分为两种:

1、经典蓝牙模块(BT):

泛指支持蓝牙协议在4.0以下的模块,一般用于数量比较的传输,如:语音、音乐等较高数据量传输。

经典蓝牙模块可再细分为传统蓝牙模块和高速蓝牙模块

高速模块相比于传输蓝牙模块速率提高到约24Mbps,是传统蓝牙模块的八倍,可以用于录像机、高清电视、PC、PMP、UMPC和打印机之间的资料传输。

2、低功耗蓝牙模块(BLE):

指支持蓝牙协议4.0或更高的模块,也成为BLE模块,最大的特点是成本和功效的降低,应用于实时性要去比较高的产品中,比如:智能家居类(蓝牙锁、蓝牙灯)、传感设备的数据发送(血压计、温度传感器)、消费类电子(电子烟、遥控玩具)等。
蓝牙低功耗技术采用可变连接时间间隔,这个间隔根据具体应用可以设置为几毫秒到几秒不等。
另外,因为BLE技术采用非常快速的连接方式,因此可以处于“非连接”状态(节省能源),此时链路两端只有在必要时才能开启链路,然后在尽可能短的时间内关闭链路。

三、蓝牙模块对比

1、按协议划分

单模蓝牙模块:是指支持蓝牙某一种协议的模块
双模蓝牙模块:是指同时支持经典蓝牙(BT)和低功耗蓝牙(BLE) 协议的模块

2、按应用分

蓝牙数据模块:一般多使用BLE地功耗蓝牙模块,拥有极低的运行和待机功耗,使用一颗纽扣电池可连续工作数年之久。
蓝牙音频模块:音频需要大码流的数据传输更适合使用BT经典蓝牙模块。

3、按照湿度等级分为:

工业级:温度范围-40摄氏度~85摄氏度,能够在室外、干扰大等恶劣环境下正常运行
商业级:温度范围为0摄氏度~70摄氏度,一般应用在普通民用产品中。

四、集成

iOS对蓝牙库进行了封装,封装在CoreBluetooth库

import CoreBluetooth

接下来是对一些代码中需要使用到的名词的备注

CBCentralManager - 中心管理者
CBPeripheralManager - 外设管理者
CBPeripheral - 外设对象
CBService - 外设服务
CBCharacteristic - 外设服务的特征

注:一个CBPeripheral可以包含多个CBService,而一个CBService也可以包含多个CBCharacteristic

接下来介绍蓝牙从打开到连接到发送数据到接收数据的一整个流程

1、设置权限,在info.plist里面加入
Privacy - Bluetooth Peripheral Usage Description
2、开发流程

 /******蓝牙******/
var bleManager: CBCentralManager? //系统蓝牙设备管理对象
var peripheral: CBPeripheral? //外围设备
var writeChar: CBCharacteristic? // 写服务的特征值

override func viewDidLoad() {
        super.viewDidLoad()
        // MARK: - 可以不用系统的蓝牙提示框,使用自定义的蓝牙提示框
        self.bleManager = CBCentralManager.init(delegate: self, queue: nil, options: nil)
       //MARK:-使用系统的蓝牙提示框
//        self.bleManager = CBCentralManager.init(delegate: self, queue: nil)
        // 设置代理
        self.bleManager?.delegate = nil
}
  • 判断蓝牙状态

// 判断蓝牙状态,通过CBCentralManager的state来获取
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
           // 确认蓝牙打开状态,然后进行一系列的操作
            // MARK: - 根据UUID筛选
//            let uuid = CBUUID(string: "0000180A-0000-1000-8000-00805F9B34FB")
//            self.bleManager?.scanForPeripherals(withServices: [uuid], options: nil)
            // MARK: - 根据名字筛选
            self.blueToothStatus = true
           self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
            GMLog("蓝牙打开,正在扫描")
        case .unknown:
            GMAlert.gm_alert(title: "", message: "蓝牙权限不清楚", cancelBtnTitle: "设置", okBtnTitle: "好") { (alertIndex) in
                
            }
        case .poweredOff:
                GMAlert.gm_alert(title: "", message: "打开蓝牙来允许\"****\"连接到配件", cancelBtnTitle: "设置", okBtnTitle: "好") {[weak self] (alertIndex) in
                    if alertIndex == .ok {
                        GMLog("ok")
                        GMAlert.gm_alertSingle(title: "提示", message: "手机蓝牙未开启\n请在设置中开启蓝牙", okBtnTitle: "确定", handler: { (alertIndex) in
                            if #available(iOS 10.0, *) {
                                self?.settingAuthority()
                            } else {
                                // Fallback on earlier versions
                            }
                        })
                    }else {
                        GMLog("设置")
                        
                        if #available(iOS 10.0, *) {
                            self?.settingAuthority()
                        } else {
                            // Fallback on earlier versions
                            GMLog("版本低于10")
                        }
                    }
                    
                }
        default:
            GMLog("----蓝牙关闭")
        }
    }
  • 扫描到外设
    // 扫描到外设
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        GMLog("----scanPeripheral: \(peripheral)") // , \(peripheral.identifier.uuidString), \(peripheral.identifier.uuid)
        GMLog("*********adver:\(advertisementData)")
        //        NSData *data = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"];
        let data = advertisementData["kCBAdvDataManufacturerData"]
        if data != nil {
            let dataString = dataToString(data: data as! Data)          
        }
        // 连接设备
        self.bleManager?.connect(peripheral, options: nil)
    }

连接成功&失败

    // 连接成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        GMLog("----连接成功")
//        GMLog("\(self.peripheral)")
        // 停止扫描
        self.bleManager?.stopScan()
        self.peripheral = peripheral
        peripheral.delegate = self
        self.peripheral?.delegate = self
        peripheral.discoverServices(nil)
//        let uuid = "9000"
//        peripheral.discoverServices([CBUUID.init(string: uuid)])
    }
    
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        GMLog("----连接失败")
    }
  • 断开设备连接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        GMLog("----断开连接, \(self.isBindState), \(String(describing: self.peripheral))")
        self.peripheral = nil
        self.writeChar = nil
// 如果需要重新扫描
        //        self.bleManager?.scanForPeripherals(withServices: nil, options: nil)
    }
  • 搜索到服务
    //一旦我们读取到外设的相关服务UUID就会回调下面的方法
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        
        for aService in peripheral.services! {
            GMLog("aaaaa-----\(aService.uuid.uuidString)")
//            let uuid = "9000"
            peripheral.discoverCharacteristics(nil, for: aService)
//            peripheral.discoverCharacteristics([CBUUID.init(string: uuid)], for: aService)
            
            // 过滤条件
            //            if aService.uuid == CBUUID.init(string: "0xFFF0") {
            //                peripheral.discoverCharacteristics(nil, for: aService)
            //            }
        }
    }
  • 搜索到特征
    // 根据特征值去判断读操作还是写操作
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
//        GMLog("!!!!!!!!!!!!!!!!\(service.characteristics)")
        for aChar in service.characteristics! {
            GMLog("#########\(aChar.uuid.uuidString)")
            self.writeChar = aChar
//            self.sendValue1()
            
            // 过滤条件
            //            if aChar.uuid == CBUUID.init(string: "0xFFF1") { // 0xFFF1 写数据
            //
            //                self.writeChar = aChar
            //            }
            //                // 0xFFF4 读数据
            //            else if aChar.uuid == CBUUID.init(string: "0xFFF4") {
            //
            //                peripheral.setNotifyValue(true, for: aChar)
            //            }
            
            if aChar.uuid == CBUUID.init(string: "9999") {
                peripheral.setNotifyValue(true, for: aChar)
            }
        }
    }
  • 收到外设消息更新
    // 发送
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        self.sendValue1()
//        if characteristic.isNotifying {
//            self.sendValue1()
//        }
    }
    
    
    // 接收
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        
        guard let data = characteristic.value else {
            return
        }
        GMLog("\(characteristic)")
        GMLog("----接收数据:\(self.dataToString(data: data))")
        let dataString = self.dataToString(data: data)
        let resultString = String(dataString.suffix(dataString.count - 12))
//        let acceptString = string(from: self.dataTo(from: resultString))
        self.deviceId = string(from: self.dataTo(from: resultString))
        GMLog("\(string(from: self.dataTo(from: resultString)))")
        
        // didDisconnectPeripheral
        
        // MARK: - 如果接收到deviceId 断开蓝牙;否则继续接收
        if self.deviceId != nil {
            if let peripheral = self.peripheral {
                self.bleManager?.cancelPeripheralConnection(peripheral)
            }
        }
        // didDisconnectPeripheral
//        self.bleManager?.cancelPeripheralConnection(self.peripheral!)
        // FIXME: - 接收到数据后请求后台接口
        self.newAnlanyData(data: data)
    }
    
    //向peripheral中写入数据后的回调函数
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
        GMLog("写入成功")
    }
    
    
    /// 新的计算方法
    private func newAnlanyData(data: Data) {
        if data[0] == 0xCF {
            GMLog("进这里")
        } else if data[0] == 0x00 && data.count == 2 {
            self.sendDeviceOff()
        }
    }
}

说明

  • 蓝牙的流程:搜索-连接-连接成功/失败(设置外设代理,搜索服务)-搜索到服务(搜索特征)-搜索到特征-监听需要的特征(读写、读、写等根据情况来确定)-通过外设读写特征写入指令-收到设备返回信息-断开连接
  • CBCentralManagerDelegate:中心管理者代理,负责搜索,设备状态的一些回调
    CBPeripheralDelegate:外设代理,负责对外设的一些操作,特征的订阅,以及设备信息和消息的更新回调

16进制的转换

16进制类型的字符串[A-F,0-9]和Data之间的转换可以使用下面的方法。如果是包含=之类的可以直接用字符串转换Data即可

extension String {
    ///16进制字符串转Data
    func hexData() -> Data? {
        var data = Data(capacity: count / 2)
        let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
        regex.enumerateMatches(in: self, range: NSMakeRange(0, utf16.count)) { match, flags, stop in
            let byteString = (self as NSString).substring(with: match!.range)
            var num = UInt8(byteString, radix: 16)!
            data.append(&num, count: 1)
        }
        guard data.count > 0 else { return nil }
        return data
    }
    func utf8Data()-> Data? {
        return self.data(using: .utf8)
    } 
}
extension Data {
    ///Data转16进制字符串
    func hexString() -> String {
        return map { String(format: "%02x", $0) }.joined(separator: "").uppercased()
    }
}

OTA升级

OTA是DFU(Device Firmware Update)的一种类型,准确说,OTA的全称应该是OTA DFU,就是设备固件升级的意思。只不过大家为了方便起见,直接用OTA来指代固件空中升级(有时候大家也将OTA称为FOTA)。
OTA升级并不复杂,只需要按照硬件定制的协议,把数据按照正常的写入方式发送给硬件即可(注意查看硬件是否规定数据的大小端)

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

你可能感兴趣的:(swift 蓝牙集成开发)