IOS--swift BLE蓝牙通信管理(多设备)

之前一直做的是Android,公司IOS端突然要同时进行多个项目,IOS同学表示压力山大,所以临危受命由我来完成项目中关于BLE通信的功能模块,由于之前做过Android版本的,并且运行状况良好,一直都比较稳定,因此分享出来,也希望大家能提出好的建议。

总共有4个swift文件。

如图:

BLEManager用于管理中心蓝牙提供扫描,延时停止扫描等功能

BLEModel是用于按照嵌入式规定的帧格式发送指令,解析指令,未加如重连。

CRC16 是用于进行CRC16校验用的

Utils 工具

附上主要代码:

//  Created by JamesWang on 2018/8/13.
//  BLE蓝牙的管理类,单例模式
//
//
//

import Foundation
import CoreBluetooth
import UIKit

class BLEManager:NSObject {
    //Drip的Serviceuuid
    let SERVICE_UUID: String = "0000ffe0-0000-1000-8000-00805f9b34fb"
    //Drip的Characteristic UUID
    let CHARACTERISTIC_UUID: String = "0000ffe1-0000-1000-8000-00805f9b34fb"
    //断开连接
    public static let STATUS_DISCONNECT:Int8 = 2
    //连接失败
    public static let STATUS_CONNECT_FAIL:Int8 = 1
    //连接成功
    public static let STATUS_CONNECT_SUCCESS:Int8 = 0
    
    //单例模式
    static let instance = BLEManager()
    
    //蓝牙中心管理器
    private var centralManager: CBCentralManager?
    
    //扫描到的设备的集合
    open var scanDevices = [CBPeripheral]()
    
    /// 返回ble状态 用作权限处理
    /// 
    /// - Returns:
    func getBLEState() -> Int {
        return (centralManager?.state.rawValue)!
    }
    
    //连接状态的设备集合
    private var bleModels = [BLEModel]()
    //代理集合
    var bleListeners = [BLEListener]()
    
    
    //初始化
    private override init() {
        super.init()
        print("init")
        centralManager = CBCentralManager.init(delegate: self, queue: .main)
        
    }
    
    /// 添加监听接口
    /// 如果之前有就覆盖
    /// - Parameter listener: 监听接口
    open func addListener(listener:BLEListener) {
        for (index,lis) in bleListeners.enumerated() {
            if(lis.getTag() == listener.getTag()) {
                bleListeners.remove(at: index)
                break
            }
        }
        bleListeners.append(listener)
    }
    
    /// 清空监听
    open func clearListener(){
        bleListeners.removeAll()
    }
    
    /// 按照tag删除指定的listener
    ///
    /// - Parameter tag: 提前设置好的tag
    open func removeListenerByTag(tag:String) {
        for (index,listener) in bleListeners.enumerated() {
            if(listener.getTag() == tag) {
                bleListeners.remove(at: index)
            }
        }
    }
    //扫描设备
    open func scan(){
        //扫描之前先清空所有的
        openbluetooth()
        scanDevices.removeAll(keepingCapacity: false)
        centralManager?.scanForPeripherals(withServices: [CBUUID.init(string:SERVICE_UUID)], options: nil)
        
    }
    
    /// 开始扫描,并且在指定的seconds秒之后停止扫描
    ///
    /// - Parameter seconds: 指定的秒数
    open func scanWithStopDelay(seconds:Int) {
        scan()
        DispatchQueue.global().async {
            sleep(UInt32(seconds))
            self.stopscan()
        }
    }
    
    //停止扫描
    open func stopscan() {
        centralManager?.stopScan()
    }
   
    
    /// 添加一个设备
    ///
    /// - Parameter blemodel: 设备模型
    func addBLEModel(blemodel:BLEModel) {
        for model in bleModels {
            if(model.bleName == blemodel.bleName) {
                return
            }
            
        }
        bleModels.append(blemodel)
    }
    
    /// 按照蓝牙名称返回对应设备
    /// - Parameter name: 要获取的设备蓝牙名称
    /// - Returns: 返回一个对应的设备,如果没有找到则返回nil
    open func getBLEModelByName(name:String) -> BLEModel?{
        for model in bleModels {
            if(model.bleName == name) {
                return model
            }
            
        }
        return nil
    }
    
    /// 外界通过该方法进行与设备的连接
    ///
    /// - Parameter peripheral: 外设
    func connect(peripheral: CBPeripheral){
        centralManager?.connect(peripheral, options: nil)
    }
    
    /// 断开连接
    ///
    /// - Parameter peripheral: <#peripheral description#>
    func disconnect(peripheral:CBPeripheral) {
        centralManager?.cancelPeripheralConnection(peripheral)
    }
    
    /// 另外一种断开连接的方法
    ///
    /// - Parameter name: 外设的名称 注意大小写
    func disconnect(name:String) {
        let blemodel = getBLEModelByName(name: name)
        disconnect(peripheral:(blemodel?.peripheral)!)
    }
}

//ble事件的代理
@objc protocol BLEListener:NSObjectProtocol {
    //必须设置tag
    func getTag() -> String
    /// 状态变更
    ///
    /// - Parameters:
    ///   - peripheral: 外设
    ///   - status: 状态
    ///             0 连接成功
    ///             1 连接失败
    ///             2 断开连接
    /// - Returns:
    @objc optional func bleStatusChange(peripheral:CBPeripheral,status:Int8)
   
    //发现新设备
    @objc optional func bleNewDevice(peripheral:CBPeripheral,RSSI:NSNumber)
    
    //读取rssi值
    @objc optional func bleRssi(peripheral:CBPeripheral,RSSI:NSNumber)
    
    /// 接受到数据 整帧的数据
    ///
    /// - Parameters:
    ///   - data: 数据 具体协议参照文档
    @objc optional func bleData(data:[Any])
}


//拓展出来蓝牙状态管理
extension BLEManager: CBCentralManagerDelegate{
    func openbluetooth() {
        if(centralManager?.state != .poweredOn) {
            showAlert()
        }
    }
    
    //判断手机蓝牙状态
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        print(central.state.rawValue)
        switch central.state {
        case .poweredOn:
            print("可用")
        case .resetting:
            print("重置中")
        case .unsupported:
            print("不支持")
        case .unauthorized:
            print("未验证")
        case .poweredOff:
            print("未启动")
        case .unknown:
            print("未知的")
        }
    }
    
   
    /** 发现符合要求的外设 */
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        // 根据外设名称来过滤
        print(peripheral.name!)
        //如果不包含 就加入
        if(!scanDevices.contains(peripheral)) {
            scanDevices.append(peripheral)
            for listener in bleListeners {
                listener.bleNewDevice?(peripheral: peripheral,RSSI:RSSI)
            }
        }
    }
    
    // 连接成功
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        self.centralManager?.stopScan()
        peripheral.discoverServices([CBUUID.init(string: SERVICE_UUID)])
        let bleModel = BLEModel(p:peripheral)
        peripheral.delegate = bleModel
        addBLEModel(blemodel: bleModel)
        print("连接成功 \(peripheral.identifier)")
        for listener in bleListeners {
            listener.bleStatusChange?(peripheral: peripheral, status: BLEManager.STATUS_CONNECT_SUCCESS)
        }
    }
    //连接失败
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("连接失败")
        for listener in bleListeners {
            listener.bleStatusChange?(peripheral: peripheral, status: BLEManager.STATUS_CONNECT_FAIL)
        }
    }
    //断开连接
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("断开连接")
        // 重新连接
        // central.connect(peripheral, options: nil)
        for listener in bleListeners {
            listener.bleStatusChange?(peripheral: peripheral, status: BLEManager.STATUS_DISCONNECT)
        }
    }
}
//
//  BLEModel.swift
//  蓝牙中心设备Swift
//
//  Created by JamesWang on 2018/8/15.
// 负责与设备通信的模块

import Foundation
import CoreBluetooth

class BLEModel:NSObject {
    var sn:String = "" //设备的sn编号 需要先调用sendRequestSN
    var status:Int8 = 0 //设备状态
    var coldTem:Float32 = 0 //冷水温度
    var hotTem:Float32 = 0 //热水温度
    var statusProgress:UInt8 = 0 //当前状态的进度值
    var electricStatusX:Int8 = 0 //x轴电机状态
    var electricStatusY:Int8 = 0 //y轴电机状态
    var electricStatusZ:Int8 = 0//z轴电机状态
    var electricStatusR:Int8 = 0  //r轴电机状态
    var waterBoxStatus:UInt8 = 0 //水箱状态
    var brewTime:Int32 = 0 //累计冲泡时间
    
    
    //////////////////////////////////////////////////
    
    let CMD_01:UInt8 = 0x01 //获取设备序列号
    let CMD_81:UInt8 = 0x81// 返回设备序列号
    
    
    /// 获取设备序列号
    func sendRequestSN() {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(4)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_01)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 解析SN
    /// 6~33    SN28个字节,高位在前
    /// - Parameter data:
    func convert81(data:[UInt8]) {
        if(data.count != 36) {
            return
        }
        var array:[UInt8] = []
        for (index,item) in data.enumerated() {
            if(index >= 6 && index <= 33) {
                array.append(item)
            }
        }
        sn = String.init(data:Data.init(bytes: array),encoding: String.Encoding.utf8).unsafelyUnwrapped
        //将所有的状态数据post出去
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_81,sn))
        }
    }
    
    //////////////////////////////////////////////////
    
    let CMD_03:UInt8 = 0x03 //获取设备状态
    let CMD_83:UInt8 = 0x83 //返回设备状态
    
    /// 请求获取设备状态
    func sendRequestStatus(){
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        data.append(int16value: 4)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_03)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 解析设备状态
    /// 例如: 66 ee 00 16 01 83 00 41 B2 66 66 41 B2 66 66 00 00 00 00 00 00 10 00 00 01 01
    ///    字节范围       含义
    ///     0-1         帧头
    ///     2-3         帧长
    ///     4           设备ID  0:保留 1:Drip  2:Drop
    ///     5           命令字 0x81
    ///     6           状态字 0:空闲(唤醒态) 1:睡眠 2:待机(唤醒态但未工作,工作取消) 3:查找中心点 4:冲泡状态 5:冲泡中止状态 6:湿滤纸态 -1:设备故障    Int8
    ///     7-10        冷水的温度                                                                                                            Float32
    ///     11-14       热水的温度                                                                                                            Float32
    ///     15          状态值 指当前状态的进度值: 查找中心点:0 -未找到中心点 1-已找到中心点 2-正在查找 冲泡状态代表进度百分比                              UInt8
    ///     16          电机故障信息  Bit0~Bit1:X轴 Bit2~Bit3:Y轴 Bit4~Bit5:Z轴 Bit6~Bit7:R轴
    ///     17          水箱故障信息
    ///     18-21       累计冲泡时间                                                                                                           Int32
    ///     22          预留
    ///     23          预留
    ///     24-25       CRC16
    /// - Parameter data:
    func convert83(data:[UInt8]) {
        //        assert(data.count == 18,"data count != 18 \(data)")
        if(data.count != 26) {
            return
        }
        status = Int8.init(bitPattern: data[6])
        //获取到冷水温度
        coldTem = Utils.convertFloat(i1: data[7], i2: data[8], i3: data[9], i4: data[10])
        //获取到热水温度
        hotTem = Utils.convertFloat(i1: data[11], i2: data[12], i3: data[13], i4: data[14])
        //获取到状态进度值
        statusProgress = data[15]
        //电机故障信息
        electricStatusX = Int8.init(bitPattern: (data[16] & 0x03))
        electricStatusY = Int8.init(bitPattern: (data[16] >> 2 & 0x03))
        electricStatusZ = Int8.init(bitPattern: (data[16] >> 4 & 0x03))
        electricStatusR = Int8.init(bitPattern: (data[16] >> 6 & 0x03))
        waterBoxStatus = data[17]
        brewTime = Utils.convertInt(i1: Int(data[18]), i2: Int(data[19]), i3: Int(data[20]), i4: Int(data[21]))
        print("解析到数据了 \(status)  \(coldTem) \(hotTem) \(statusProgress)")
        let bledata:[Any] = generateBLEData(value:peripheral?.name ?? "",CMD_83,status,coldTem,hotTem,statusProgress,electricStatusX,electricStatusY,electricStatusZ,electricStatusR,waterBoxStatus,brewTime)
        //将所有的状态数据post出去
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: bledata)
        }
    }
    
    
    //////////////////////////////////////////////////
    
    let CMD_04:UInt8 = 0x04 //设置设备参数
    let CMD_84:UInt8 = 0x84 //返回设置参数响应
    
    /// 设置设备参数
    ///
    /// - Parameters:
    ///   - sleepMode: 休眠模式 UInt8   0: 没有休眠模式    1:休眠模式1(台上休眠)    2:休眠模式2(台下休眠)默认
    ///   - idleTime: 休眠开启时间 空闲时间  UInt16   0~65535 秒
    ///   - preWater: 预先出水量 UInt8  0~255 毫升,将管内冷水回抽后,预泵出的热水水量
    ///   - boilerTem: 锅炉温度 Float32 热水温度值
    ///   - totalTrip: z轴总行程 UInt16 Z轴上升的最大高度(默认475mm)
    ///   - searchHeight: 寻找高度 UInt16 寻找咖啡杯时,Z轴上升的高度值,根据不同的杯子、咖啡量进行调节匹配。
    ///   - armLength: 上臂总长度  UInt16    Z轴行程=冲泡高度+上臂长度(默认220mm)
    ///   - horizontalOffset: 水平偏移 Int8  设置水平方向像素偏移值(-80~80)
    ///   - verticalOffset: 垂直偏移  Int8  设置垂直方向像素偏移值(-60~60)
    ///   - angleWake: 唤醒角度  UInt8  唤醒时R轴旋转的角度 范围0~198
    func sendSetDatas(sleepMode:UInt8,idleTime:UInt16,preWater:UInt8,boilerTem:Float32,totalTrip:UInt16,searchHeight:UInt16,armLength:UInt16,horizontalOffset:Int8,verticalOffset:Int8,angleWake:UInt8)
    {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        data.append(int16value: 21)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_04)
        //休眠模式 UInt8
        data.append(sleepMode)
        //休眠开启时间 空闲时间  UInt16
        data.append(uint16value: idleTime)
        //预先出水量 UInt8
        data.append(preWater)
        //锅炉温度 Float32
        data.append(float32value: boilerTem)
        //z轴总行程 UInt16
        data.append(uint16value: totalTrip)
        //寻找高度 UInt16
        data.append(uint16value: searchHeight)
        //上臂总长度  UInt16
        data.append(uint16value: armLength)
        //horizontalOffset: 水平偏移 Int8
        data.append(UInt8.init(bitPattern: horizontalOffset))
        //verticalOffset: 垂直偏移  Int8
        data.append(UInt8.init(bitPattern: verticalOffset))
        //angleWake UInt8
        data.append(angleWake)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
        
    }
    
    /// 返回设置设备参数
    /// 例如: 66 ee 00 03 02 82 00 42 d5
    ///    字节范围       含义
    ///     0-1         帧头
    ///     2-3         帧长
    ///     4           帧类别
    ///     5           命令字
    ///     6           0:OK 1:失败
    ///     7-8         CRC校验
    /// 因为此帧只会传递过来ok结果 因此暂时认为一但接受到该帧就认为是参数设置成功了
    /// - Parameter data:
    func convert84(data:[UInt8]) {
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_84,data[6]))
        }
    }
    
    
    
    
    
    //////////////////////////////////////////////////
    

    let CMD_05:UInt8 = 0x05 //获取设备参数
    let CMD_85:UInt8 = 0x85 //返回设备参数
    
    /// 请求设备参数
    func sendRequestDatas() {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        data.append(int16value: 4)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_05)
        
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    
    
    
    /// 解析设备参数
    /// 例如:
    ///    字节范围       含义
    ///     0-1         帧头
    ///     2-3         帧长
    ///     4           设备类别 drip01
    ///     5           命令字 0x85
    ///     6           休眠模式 0: 没有休眠模式 1:休眠模式1(上面)  2:休眠模式2(下面)默认   UInt8
    ///     7-8         休眠开启时间  0~65536 秒                                       UInt16
    ///     9           预先出水量   0~255 毫升                                        UInt8
    ///     10-13       锅炉的温度   温度  (浮点数)                                    Float32
    ///     14-15       总行程 Z轴总行程(默认485mm) L                                   Int16
    ///     16-17       寻找高度   寻找咖啡杯时,Z轴上升的高度值,根据不同的杯子、咖啡量进行调节匹配         UInt16
    ///     18-19       上臂长度    (默认230mm) B                                      UInt16
    ///     20          水平偏移    设置水平方向像素偏移值(-80~80)                       Int8
    ///     21          垂直偏移    设置垂直方向像素偏移值(-60~60)                       Int8
    ///     22          角度值     R轴唤醒偏移角度 范围0~255                             UInt8
    ///     23-24       CRC校验
    /// 因为此帧只会传递过来ok结果 因此暂时认为一但接受到该帧就认为是参数设置成功了
    /// - Parameter data:
    func convert85(data:[UInt8]) {
        
        if(data.count != 25) {
            return
        }
        //长度ok 开始具体的解析
        //创建一个存储参数的集合 将参数存储到该集合中
        var bledata:[Any] = []
        bledata.append(peripheral?.name ?? "")
        bledata.append(CMD_85)
        //存入休眠模式  无符号UInt8
        bledata.append(data[6])
        //存入休眠开启时间 无符号UInt16
        bledata.append(Utils.convertUShort(i1: data[7], i2: data[8]))
        //存入预先出水量 无符号UInt8
        bledata.append(data[9])
        //存入锅炉的温度   Float32
        bledata.append(Utils.convertFloat(i1: data[10], i2: data[11], i3: data[12], i4: data[13]))
        //存入Z总行程     有符号 Int16
        bledata.append(Utils.convertShort(i1: data[14], i2: data[15]))
        //寻找高度   无符号 UInt16
        bledata.append(Utils.convertUShort(i1: data[16], i2: data[17]))
        //上臂长度      无符号 UInt16
        bledata.append(Utils.convertUShort(i1: data[18], i2: data[19]))
        //水平偏移      Int8
        bledata.append(Int8.init(bitPattern: data[20]))
        //垂直偏移      Int8
        bledata.append(Int8.init(bitPattern: data[21]))
        //角度值     R轴唤醒偏移角度 UInt8
        bledata.append(data[22])
        //将所有的状态数据post出去 需要在
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: bledata)
        }
    }
    
    //////////////////////////////////////////////////
    let CMD_06:UInt8 = 0x06 //切换通信模式
    let CMD_86:UInt8 = 0x86 //返回切换通信模式
    
    /// 切换通信模式
    func sendRequestSwitchMode() {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        data.append(int16value: 4)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_06)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    
    /// 切换通信模式响应
    ///
    ///    字节范围       含义
    ///     0-1         帧头
    ///     2-3         帧长
    ///     4           帧类别
    ///     5           命令字 0X84
    ///     6-7         CRC16
    
    /// - Parameter data:
    func convert86(data:[UInt8]){
        if(data.count != 8) {
            return
        }
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_86))
        }
    }
    
    //////////////////////////////////////////////////
    let CMD_07:UInt8 = 0x07 //WiFi信息配置
    let CMD_87:UInt8 = 0x87 //返回WiFi信息配置
    
    
    /// 设置wifi的ssid
    ///
    /// - Parameter ssid: ssid
    func sendSetWifiSSID(ssid:String) {
        let byteArray = [UInt8](ssid.utf8)
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(byteArray.count + 5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_07)
        //控制字
        data.append(UInt8.init(bitPattern: 0x01))
        //ssid
        data.append(contentsOf: byteArray)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 设置wifi的密码
    ///
    /// - Parameter pwd: wifi密码
    func sendSetWifiPwd(pwd:String) {
        let byteArray = [UInt8](pwd.utf8)
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(byteArray.count + 5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_07)
        //控制字
        data.append(UInt8.init(bitPattern: 0x02))
        //密码
        data.append(contentsOf: byteArray)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    //6    控制字    1:SSID2:Password
    func convert87(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_87,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    let CMD_08:UInt8 = 0x08 //写冲泡文件
    let CMD_88:UInt8 = 0x88 //返回写冲泡文件
    
    
    /// 发送Profile名称
    ///总长度不要超过100个字节
    /// - Parameter profileName: profile的名称
    private func sendProfileName(profileName:String) -> Bool{
        let byteArray = [UInt8](profileName.utf8)
        loopcount = 0 //循环号归0
        if(byteArray.count > 100) {
            print("总长度不要超过100个字节,count= \(byteArray.count)" )
            return false
        }
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(byteArray.count + 6)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_08)
        //控制字
        data.append(UInt8.init(bitPattern: 0x00))
        //文件内容
        data.append(contentsOf: byteArray)
        //循环号
        data.append(loopcount)
        loopcount += 1
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        //尝试发送次数为3次
        A:for _ in (0..<3) {
            send(data: data.toArray(type: UInt8.self))
            let start = Int(Date().timeIntervalSince1970) //获取系统当前秒数
            while(Int(Date().timeIntervalSince1970) - start < BLEModel.MAX_WAIT && !isNameCome && !isResendCome && !isErrorCome) {
                
            }
            print("isnamecome = \(isNameCome)")
            if(isNameCome) {
                
                isNameCome = false //重置
                return true
            }
            else {
                continue A
            }
        }
        return false
        
    }
    
    /// 发送设置Profile的总长度
    ///
    /// - Parameter profileSize: profile的大小
    private func sendProfileSize(profileSize:UInt32) -> Bool {
        print("sendProfileSize")
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(10)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_08)
        //控制字
        data.append(UInt8.init(bitPattern: 0x01))
        //文件内容
        data.append(uint32value: profileSize)
        //循环号
        data.append(loopcount)
        loopcount += 1
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //尝试发送次数为3次
        A:for _ in (0..<3) {
            send(data: data.toArray(type: UInt8.self))
            let start = Int(Date().timeIntervalSince1970) //获取系统当前秒数
            while(Int(Date().timeIntervalSince1970) - start < BLEModel.MAX_WAIT && !isSizeCome && !isResendCome && !isErrorCome) {}
            if(isSizeCome) {
                isSizeCome = false //重置
                return true
            }
            else {
                continue A
            }
        }
        return false
    }
    
    
    
    /// 按照1K的限制来切割数据
    /// 需要在子线程中发送profile
    ///
    /// - Parameter profileContent:
    private  func sendProfileContent(profileContent:String) ->Bool {
        print(profileContent)
        var byteArray:[UInt8] = [UInt8](profileContent.utf8)//将profile转换为[UInt8]
        let arrayCount = byteArray.count / BLEModel.LIMIT_SIZE + 1
        var bufArrays:[[UInt8]] = []
        
        for _ in (0..<arrayCount) {
            var buf:[UInt8] = [] //创建一个临时数组集合
            while(buf.count < BLEModel.LIMIT_SIZE && byteArray.count > 0) {
                buf.append(byteArray.removeFirst())
            }
            bufArrays.append(buf)
        }
        Big:
            for array in bufArrays {
                var data = Data()
                //组装头
                data.append(contentsOf: HEAD)
                //帧长
                let len:Int16 = (Int16)(array.count + 6)
                data.append(int16value: len)
                //设备ID
                data.append(0x01)
                //命令
                data.append(CMD_08)
                //控制字
                data.append(UInt8.init(bitPattern: 0x02))
                //内容
                data.append(contentsOf: array)
                //循环号
                data.append(loopcount)
                loopcount += 1
                //CRC16检验
                data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
                A:
                    for _ in (0..<3) {
                        //发送数据
                        send(data: data.toArray(type: UInt8.self))
                        let start = Int(Date().timeIntervalSince1970) //获取系统当前秒数
                        while (Int(Date().timeIntervalSince1970) - start < BLEModel.MAX_WAIT && !isContentCome && !isResendCome && !isErrorCome) {}
                        if (isContentCome) {
                            isContentCome = false
                            continue Big
                        } else {
                            continue A
                        }
                }
                
                return false
        }
        return true
    }
    
    
    /// 发送传输结束
    private func sendProfileFinish() -> Bool{
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(10)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_08)
        //控制字
        data.append(UInt8.init(bitPattern: 0x03))
        //循环号
        data.append(loopcount)
        loopcount += 1
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        send(data: data.toArray(type: UInt8.self))
        
        //尝试发送次数为3次
        A:for _ in (0..<3) {
            send(data: data.toArray(type: UInt8.self))
            let start = Int(Date().timeIntervalSince1970) //获取系统当前秒数
            while(Int(Date().timeIntervalSince1970) - start < BLEModel.MAX_WAIT && !isFinishCome && !isResendCome && !isErrorCome) {}
            if(isSizeCome) {
                isSizeCome = false //重置
                return true
            }
            else {
                continue A
            }
        }
        return false
    }
    
    /// 取消传输
    func sendProfileCancel() {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(10)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_08)
        //控制字
        data.append(UInt8.init(bitPattern: 0x04))
        //循环号
        data.append(loopcount)
        loopcount += 1
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        send(data: data.toArray(type: UInt8.self))
    }
    
    
    
    
    /// 发送profile
    ///
    /// - Parameters:
    ///   - name: profile的名称
    ///   - content: profile的内容
    ///   - isDefault: 是否设置为默认Profile
    func sendProfile(name:String,content:String,isDefault:Bool) {
        isNameCome = false //名称发送是否正常响应
        isResendCome = false//重新发送是否正常响应
        isErrorCome = false//失败错误发送是否正常响应
        isSizeCome = false //大小发送是否正常响应
        isContentCome = false //内容发送是否正常响应
        isFinishCome = false //完成发送是否正常响应
        isCancelCome = false //取消发送是否正常响应
        isSetDefaultCome = false //设置为默认是否为正常响应
        isJustSend = false //设置为是否只是发送Profile的flag
        
        DispatchQueue.global().async {
            let byteArray:[UInt8] = [UInt8](content.utf8)//将profile转换为[UInt8]
            if (isDefault) {
                self.isJustSend = true
                if(!self.sendSetDefaultProfile(name:name)) { //发送是否设置为默认
                    return
                }
            } else {
                self.isJustSend = false
            }
            if (!self.sendProfileName(profileName: name)) { //发送profile名称
                return
            }
            
            if (!self.sendProfileSize(profileSize:UInt32(byteArray.count))) { //发送大小
                return
            }
            if (!self.sendProfileContent(profileContent:content)) { //发送内容
                return
            }
            if (!self.sendProfileFinish()) { //发送是否结束
                return
            }
        }
    }
    
    
    /// 返回传送Profile状态
    ///
    /// - Parameter data: <#data description#>
    func convert88(data:[UInt8]) {
        if(data.count != 10) {
            return
        }
        let status = Int8.init(bitPattern: data[6])
        print("convert85 = \(status)")
        
        switch status {
        case 0: //文件名传输成功
            isNameCome = true
        case 1://1:文件总长度
            isSizeCome = true
        case 2: //传输中
            isContentCome = true
        case 3: //传输结束 检查是否只是发送 判断是否开始搜寻cup
            isFinishCome = true
            if (!isJustSend) {
                sendSetStatus(status:0x04);//查找中心点
            }
        case 4: //取消传输
            isCancelCome = true
        case 5://检验错误 重发
            isResendCome = true
        case -1: //故障
            isErrorCome = true
        default:
            break
        }
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_88,status))
        }
    }
    
    
    
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    let CMD_09:UInt8 = 0x09 //控制设备运行状态
    let CMD_89:UInt8 = 0x89 //返回控制设备运行状态
    
    /// 设置设备状态 0:空闲
    ///    1:进入休眠
    ///    2:唤醒设备
    ///    3:进入IAP模式
    ///    4:查找中心点
    ///    5:停止查找中心点(回到复位位置)
    ///    6:开始冲泡
    ///    7:暂停冲泡
    ///    8:继续冲泡
    ///    9:结束冲泡
    /// see BLEModel.ACTION...
    func sendSetStatus(status:Int8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_09)
        //控制字
        data.append(UInt8.init(bitPattern: status))
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 返回控制设备运行状态
    ///
    /// - Parameter data: <#data description#>
    func convert89(data:[UInt8])  {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_89,data[6]))
        }
    }
    
    
    //////////////////////////////////////////////////
    let CMD_0A:UInt8 = 0x0A //设置本地默认冲泡文件
    let CMD_8A:UInt8 = 0x8A //返回设置本地默认冲泡文件
    
    /// 设置为默认的Profile
    ///
    /// - Parameter name: profile的名称
    /// - Returns: 是否设置成功
    private func sendSetDefaultProfile(name:String) -> Bool {
        
        self.isSetDefaultCome = false
        
        let byteArray = [UInt8](name.utf8)
        loopcount = 0 //循环号归0
        if(byteArray.count > 100) {
            print("总长度不要超过100个字节,count= \(byteArray.count)" )
            return false
        }
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(byteArray.count + 6)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_0A)
        //文件内容
        data.append(contentsOf: byteArray)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        //尝试发送次数为3次
        A:for _ in (0..<3) {
            send(data: data.toArray(type: UInt8.self))
             //获取系统当前秒数
            let start = Int(Date().timeIntervalSince1970)
            while(Int(Date().timeIntervalSince1970) - start < BLEModel.MAX_WAIT && !isSetDefaultCome && !isResendCome && !isErrorCome) {
                
            }
            if(isSetDefaultCome) {
                //重置
                isSetDefaultCome = false
                
                return true
            }
            else {
                continue A
            }
        }
        return false
        
    }
    
    
    /// 返回设置本地默认冲泡文件
    ///
    /// - Parameter data: <#data description#>
    func convert8A(data:[UInt8])  {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_8A,data[6]))
        }
    }
    
    
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    let CMD_0B:UInt8 = 0x0B //设置灯头颜色值
    let CMD_8B:UInt8 = 0x8B //返回灯头颜色设置
    /// 设置灯头颜色值
    ///
    /// - Parameters:
    ///   - mode: 0:RGB  1:HSV
    ///   - ledMode: 0:长亮 1:单闪   2:双闪 3:呼吸  4::关闭
    ///   - first: R/H值  mode为0时表示R值, mode为1时表示H值, R范围:0~255 H范围:0~360
    ///   - second: G/S值
    ///   - third: B/V值
    func sendSetColor(mode:UInt8,ledMode:UInt8,first:UInt8,second:UInt8,third:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(9)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_0B)
        //颜色模式
        data.append(mode)
        //LED模式
        data.append(ledMode)
        //R/H值
        data.append(first)
        //G/S值
        data.append(second)
        // B/V 值
        data.append(third)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 返回灯头颜色设置
    ///
    /// - Parameter data: <#data description#>
    func convert8B(data:[UInt8])  {
        if(data.count != 9) {
            return
        }
//      let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_8B,data[6]))
        }
    }
    
    
    //////////////////////////////////////////////////
    let CMD_0C:UInt8 = 0x0C //出水模式设置
    let CMD_8C:UInt8 = 0x8C //返回出水模式设置
    
    ///出水模式设置
    /// - Parameters:
    ///   - size: 出水量0~0xffff ml
    ///   - tem: 出水温度    水温:常温~95℃
    ///   - time: 出水时间    单位:0~255秒    UInt8
    ///   - height: 出水高度    0~240mm
    ///   - type: 杯型    0~255
    func sendSetOutWaterMode(size:UInt16,tem:UInt8,time:UInt8,height:UInt8,type:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(10)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_0C)
        //出水量
        data.append(uint16value: size)
        //出水温度
        data.append(tem)
        //出水时间
        data.append(time)
        //出水高度
        data.append(height)
        //杯型
        data.append(type)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    /// 返回出水模式设置
    ///
    /// - Parameter data: <#data description#>
    func convert8C(data:[UInt8])  {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_8C,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    let CMD_0D:UInt8 = 0x0D //读取R轴编码器值
    let CMD_8D:UInt8 = 0x8D //返回读取R轴编码器值
    
    ///读取R轴编码器值
    func sendRequestREncodeValue(){
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(4)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_0D)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 返回R轴编码器值
    ///
    /// - Parameter data: <#data description#>
    func convert8D(data:[UInt8]) {
        if(data.count != 10) {
            return
        }
        let rEncodeValue:UInt16 = Utils.convertUShort(i1: data[6], i2: data[7])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_8D,rEncodeValue))
        }
    }
    
    
    //////////////////////////////////////////////////
    
    let CMD_0E:UInt8 = 0x0D //设置R轴编码器值(直立态)
    let CMD_8E:UInt8 = 0x8D //返回R轴编码器设置结果
    
    ///设置R轴编码器值(直立态)
    ///
    /// - Parameter value: R轴编码器值
    func sendSetREncodeStraight(value:UInt16) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(6)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_0E)
        //R轴编码器值(直立态)
        data.append(uint16value: value)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 返回R轴编码器设置结果
    ///
    /// - Parameter data: <#data description#>
    func convert8E(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_8E,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    let CMD_0F:UInt8 = 0x0F //设置R轴角度(相对值)
    let CMD_8F:UInt8 = 0x8F //返回R轴角度(绝对值)
    
    
    ///设置R轴角度(相对值)
    ///
    /// - Parameter angle: 角度值 取值范围:-180°~ 180° 正值:顺时针转;负值:逆时针转
    func sendSetRAngleRelative(angle:Float32) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(8)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_0F)
        //R轴角度 相对值
        data.append(float32value: angle)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 32.返回R轴角度(绝对值)
    ///
    /// - Parameter data: 6-9
    func convert8F(data:[UInt8]) {
        if(data.count != 12) {
            return
        }
        let angle:Float = Utils.convertFloat(i1: data[6], i2: data[7], i3: data[8], i4: data[9])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_8F,angle))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    let CMD_10:UInt8 = 0x10 //设置X轴位置
    let CMD_90:UInt8 = 0x90 //设置X轴位置响应
    
    ///设置X轴位置
    ///
    /// - Parameter offSet: <#offSet description#>
    func sendPositionX(offSet:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_10)
        //R轴角度 相对值
        data.append(offSet)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 设置X轴位置响应
    ///
    /// - Parameter data: <#data description#>
    func convert90(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_90,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    let CMD_11:UInt8 = 0x11 //设置Y轴位置
    let CMD_91:UInt8 = 0x91 //设置Y轴位置响应
    
    ///设置Y
    ///
    /// - Parameter offSet: <#offSet description#>
    func sendAngleY(offSet:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_11)
        //R轴角度 相对值
        data.append(offSet)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    func convert91(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_91,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    let CMD_12:UInt8 = 0x12 //设置Z轴位置
    let CMD_92:UInt8 = 0x92 //设置Z轴位置响应
    
    ///设置Z轴位置
    ///
    /// - Parameter offSet: <#offSet description#>
    func sendPositionZ(offSet:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_12)
        //R轴角度 相对值
        data.append(offSet)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    func convert92(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_92,data[6]))
        }
    }
    
    
    
     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    
    let CMD_13:UInt8 = 0x13 //设置寻找模式
    let CMD_93:UInt8 = 0x93 //设置寻找模式响应
    
    /// 设置寻找模式
    ///
    /// - Parameter mode: 寻找模式  0:寻找中心点模式 1:定点冲泡模式
    func sendSetSearchMode(mode:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(5)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_13)
        //寻找模式
        data.append(mode)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 设置寻找模式响应
    ///
    /// - Parameter data: <#data description#>
    func convert93(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_93,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    let CMD_14:UInt8 = 0x14 //设置咖啡颜色LAB值(摄像头识别的色块)
    let CMD_94:UInt8 = 0x94 //返回LAB值设置状态
    
    /// 设置咖啡颜色LAB值(摄像头识别的色块)
    ///
    /// - Parameters:
    ///   - minL: L最小值    取值范围:0~100
    ///   - maxL: L最大值    取值范围:0~100
    ///   - minA: A最小值    取值范围:-120~120
    ///   - maxA: A最大值    取值范围:-120~120
    ///   - minB: B最小值    取值范围:-120~120
    ///   - maxB: B最大值    取值范围:-120~120
    func sendSetCoffeeColorLABValue(minL:Int8,maxL:Int8,minA:Int8,maxA:Int8,minB:Int8,maxB:Int8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(10)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_14)
        //minL: L最小值
        data.append(UInt8.init(bitPattern: minL))
        //maxL: L最大值
        data.append(UInt8.init(bitPattern: maxL))
        //minA
        data.append(UInt8.init(bitPattern: minA))
        //maxA
        data.append(UInt8.init(bitPattern: maxA))
        //minB
        data.append(UInt8.init(bitPattern: minB))
        //maxB
        data.append(UInt8.init(bitPattern: maxB))
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// 返回LAB值设置状态
    ///
    /// - Parameter data: <#data description#>
    func convert94(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_94,data[6]))
        }
    }
    
    
    
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    let CMD_15:UInt8 = 0x15 //设置咖啡识别色块尺寸(摄像头识别的色块)
    let CMD_95:UInt8 = 0x95 //返回识别色块尺寸设置状态
    
    
    ///设置咖啡识别色块尺寸(摄像头识别的色块)
    ///
    /// - Parameters:
    ///   - minWidth: 最小宽度
    ///   - maxWidth: 最大宽度
    ///   - minHeight: 最小高度
    ///   - maxHeight: 最大高度
    func sendSetCoffeeSize(minWidth:UInt8,maxWidth:UInt8,minHeight:UInt8,maxHeight:UInt8) {
        var data = Data()
        //组装头
        data.append(contentsOf: HEAD)
        //帧长
        let len:Int16 = (Int16)(8)
        data.append(int16value: len)
        //设备ID
        data.append(0x01)
        //命令
        data.append(CMD_15)
        //minW
        data.append(minWidth)
        //maxW
        data.append(maxWidth)
        //minH
        data.append(minHeight)
        //maxH
        data.append(maxHeight)
        //CRC16检验
        data.append(contentsOf: CRC16.instance.getCRCResult(data:data.toArray(type: UInt8.self)))
        //发送数据
        sendAsync(data: data.toArray(type: UInt8.self))
    }
    
    /// <#Description#>
    ///
    /// - Parameter data: <#data description#>
    func convert95(data:[UInt8]) {
        if(data.count != 9) {
            return
        }
//        let status:Int8 = Int8.init(bitPattern: data[6])
        for listener in BLEManager.instance.bleListeners {
            listener.bleData?(data: generateBLEData(value: peripheral?.name ?? "",CMD_95,data[6]))
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    let MAX_COUNT = 2048
    
    let semaphore = DispatchSemaphore(value: 1)
    var peripheral: CBPeripheral?
    var bleName:String//设备蓝牙名称
    
    let HEAD:[UInt8] = [0x66,0xee]
    
    private var characteristic: CBCharacteristic?
    let queue = DispatchQueue.global()
    
    private var data=[UInt8]()//缓存数组
    
    private var lock:NSLock = NSLock()
    private var sendLock:NSLock = NSLock()
    
    init(p: CBPeripheral) {
        peripheral = p
        bleName=(peripheral?.name)!
        
    }
    var loopcount:UInt8 = 0 //循环号
   
    
    public static  let STATUS_IDLE:Int8 = 0x00 //空闲
    public static let STATUS_SLEEP:Int8 = 0x01 //睡眠
    public static let STATUS_STANDBY:Int8 = 0x02 // 待机
    public static let STATUS_SEARCHING:Int8 = 0x03 //查找中心点
    public static let STATUS_WORKING:Int8 = 0x04 //冲泡状态
    public static let STATUS_PAUSE:Int8 = 0x05 //冲泡中止状态
    public static let STATUS_WET_PAPER:Int8 = 0x06//湿滤纸态
    public static let STATUS_ERROR:Int8 = -1 //设备故障
    
    
    public static let ACTION_IDLE:Int8 = 0x00//设置空闲动作
    public static let ACTION_SLEEP:Int8 = 0x01 //设置休眠动作
    public static let ACTION_WAKE:Int8 = 0x02 //设置唤醒动作
    public static let ACTION_IAP:Int8 = 0x03 //设置IAP
    public static let ACTION_SEARCH:Int8 = 0x04 //设置查找中心点动作
    public static let ACTION_PAUSE_SEARCH:Int8 = 0x05 //设置停止查找动作
    public static let ACTION_START_BREW:Int8 = 0x06 //设置开始冲泡动作
    public static let ACTION_PAUSE_BREW:Int8 = 0x07//暂停冲泡动作
    public static let ACTION_RESUME_BREW:Int8 = 0x08 //继续冲泡动作
    public static let ACTION_END_BREW:Int8 = 0x09 //结束冲泡动作
    

    
    public static let LIMIT_SIZE = 1000 //单包最大长度
    public static let MAX_WAIT = 100 //发送Profile timeout 为3秒
    
    
    
    private var isNameCome:Bool = false //名称发送是否正常响应
    private var isResendCome:Bool = false//重新发送是否正常响应
    private var isErrorCome:Bool = false//失败错误发送是否正常响应
    private var isSizeCome:Bool = false //大小发送是否正常响应
    private var isContentCome:Bool = false //内容发送是否正常响应
    private var isFinishCome:Bool = false //完成发送是否正常响应
    private var isCancelCome:Bool = false //取消发送是否正常响应
    private var isSetDefaultCome:Bool = false //设置为默认是否为正常响应
    private var isJustSend:Bool = false //设置为是否只是发送Profile的flag
    
}






// MARK: - 通信协议
extension BLEModel{
  
    func generateBLEData(value : Any...) -> [Any]{
        var val:[Any] = []
        for item in value {
            val.append(item)
        }
        print("genate data = \(val)")
        return val
    }
    

    
    //解析现有数据
    func convert(){
        if(self.data.count > self.MAX_COUNT) {//当字节数组大于MAX_COUNT就清空
            self.data.removeAll()
            return
        }
        else if(self.data.count > 5) {//只有大于5个字节的时候才去解析
            let h0 = self.data[0]
            let h1 = self.data[1]
            //只有是帧头才会解析下去
            if(h0 == 0x66 && h1 == 0xee) {
                let len0 = self.data[2]
                let len1 = self.data[3]
                let frameLen:Int16 = Utils.convertShort(i1: len0, i2: len1)
                //              assert(self.data.count >= frameLen + 2 + 1,"总长度小于单帧长度")
                if(self.data.count < frameLen + 2 + 1) {//如果总长度小于该帧长度 就不解析
                    return
                }
                else {
                    let _ = self.data[4]//设备类别 1drip
                    let frameCmd = self.data[5]
                    let cmddata = Array(self.data[0...Int(frameLen+3)])//已确定data中有一帧的数据量直接截取前部分作为一个单独的Data处理
                    self.data.removeSubrange(0...Int(frameLen+3))//删除这一部分帧
                    switch frameCmd {
                    case CMD_81://返回设备序列号
                        convert81(data:cmddata)
                    case CMD_83://返回设备状态
                        convert83(data:cmddata)
                    case CMD_84://返回设置设备参数
                        convert84(data:cmddata)
                    case CMD_85://解析设备参数
                        convert85(data:cmddata)
                    case CMD_86://切换通信模式响应
                        convert86(data:cmddata)
                    case CMD_87://返回WiFi信息配置
                        convert87(data:cmddata)
                    case CMD_88://返回传送Profile状态
                        convert88(data:cmddata)
                    case CMD_89://返回控制设备运行状态
                        convert89(data:cmddata)
                    case CMD_8A://返回设置本地默认冲泡文件
                        convert8A(data:cmddata)
                    case CMD_8B://返回灯头颜色设置
                        convert8B(data:cmddata)
                    case CMD_8C://返回出水模式设置
                        convert8C(data:cmddata)
                    case CMD_8D://返回读取R轴编码器值
                        convert8D(data:cmddata)
                    case CMD_8E://返回R轴编码器设置结果
                        convert8E(data:cmddata)
                    case CMD_8F://返回R轴角度(绝对值)
                        convert8F(data:cmddata)
                    case CMD_90://设置X轴位置响应
                        convert90(data:cmddata)
                    case CMD_91://返回R轴角度(绝对值)
                        convert91(data:cmddata)
                    case CMD_92://设置Y轴位置响应
                        convert92(data:cmddata)
                    case CMD_93://设置寻找模式响应
                        convert93(data:cmddata)
                    case CMD_94://返回LAB值设置状态
                        convert94(data:cmddata)
                    case CMD_95://返回识别色块尺寸设置状态
                        convert95(data:cmddata)
                    default:
                        break
                    }
                    convert()
                    
                }
            } else {//如果不是头信息 就删除第一个字节 继续解析
                self.data.removeFirst()
                print("继续解析")
                convert()//继续解析
            }
        }
    }
    

    /// 向蓝牙外设发送数据
    ///
    /// - Parameter data: 要发送的数据
    func send(data:[UInt8] ){
        self.sendLock.lock()
        var d:[UInt8] = []
        d.append(contentsOf: data)
        
        let mtu = 20//单次传输最大字节数
        var dt:[UInt8] = []
        
        while(d.count>0) {
            while(dt.count < mtu && d.count > 0) {
                dt.append(d.removeFirst())
            }
            self.semaphore.wait()
            //发送数据
            self.peripheral?.writeValue(Data.init(bytes: dt), for: self.characteristic!,type:CBCharacteristicWriteType.withResponse)
            dt.removeAll()
        }
        self.sendLock.unlock()
        
        
    }
    
    /// 向蓝牙外设发送数据
    ///
    /// - Parameter data: 要发送的数据
    func sendAsync(data:[UInt8] ){
        DispatchQueue.global().async {
            self.send(data:data)
        }
    }
    
}

extension BLEModel:CBPeripheralDelegate{
    /** 发现服务 */
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        for service: CBService in peripheral.services! {
            print("外设中的服务有:\(service)")
        }
        //本例的外设中只有一个服务
        let service = peripheral.services?.last
        // 根据UUID寻找服务中的特征
        peripheral.discoverCharacteristics([CBUUID.init(string: BLEManager.instance.CHARACTERISTIC_UUID)], for: service!)
        //peripheral.discoverCharacteristics(nil, for: service!)
    }
    
    /** 发现特征 */
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        for characteristic: CBCharacteristic in service.characteristics! {
            print("外设中的特征有:\(characteristic)")
        }
        
        self.characteristic = service.characteristics?.last
        // 读取特征里的数据
        peripheral.readValue(for: self.characteristic!)
        // 订阅
        peripheral.setNotifyValue(true, for: self.characteristic!)
        peripheral.readRSSI()
        
        
    }
    
    
    /** 订阅状态 */
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("订阅失败: \(error)")
            return
        }
        if characteristic.isNotifying {
            print("订阅成功")
        } else {
            print("取消订阅")
        }
    }
    
    
    
    //接收到数据
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        lock.lock()
        let tdarray = characteristic.value.unsafelyUnwrapped.toArray(type: UInt8.self)
        print("新数据=\(Utils.byteArrayToHexString(tdarray))")
        for b in tdarray {
            self.data.append(b)
        }
        //开始解析
        convert()
        lock.unlock()
    }
   
    
    //写入数据
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        //        print("写入数据\(characteristic) \(error.debugDescription) \(error?.localizedDescription)")
        //释放信号量
        semaphore.signal()
    }
    
    //接收到RSSI
    func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI:NSNumber, error: Error?){
        DispatchQueue.main.asyncAfter(deadline:DispatchTime.now() + 2,execute:{
            peripheral.readRSSI()
        })
    }
}
//
//  CRC16.swift
//
//  Created by JamesWang on 2018/8/22.
//

import Foundation

class CRC16:NSObject{
    //单例模式
    static let instance = CRC16()
    
    private var crcTable: [Int] = []
    ///多项式 CRC-16/XMODEM
    private let gPloy = 0x1021
    
    //初始化
    private override init() {
        super.init()
        computeCrcTable()
    }
    
    
    
    /// 传入[UInt8]返回CRC16检验数据
    ///
    /// - Parameter data: <#data description#>
    /// - Returns: <#return value description#>
    func getCRCResult (data: [UInt8]) -> [UInt8] {
        var crc = getCrc(data:data)
        var crcArr: [UInt8] = [0,0]
        ////        Swift3.0
        for i in (0..<2).reversed() {
            crcArr[i] = UInt8(crc % 256)
            crc >>= 8
        }
//        for (var i = 1; i >= 0; i -= 1) {
//
//        }
        return crcArr
    }
    
    /**
     Generate CRC16 Code of 0-255
     */
    private func computeCrcTable() {
        for i in 0..<256 {
            crcTable.append(getCrcOfByte(aByte:i))
        }
    }
    
    private func getCrcOfByte(aByte: Int) -> Int {
        var value = aByte << 8
        for _ in 0 ..< 8 {
            if (value & 0x8000) != 0 {
                value = (value << 1) ^ gPloy
            }else {
                value = value << 1
            }
        }
        
        value = value & 0xFFFF 
        
        return value
    }
    
    private func getCrc(data: [UInt8]) -> UInt16 {
        var crc = 0
        let dataInt: [Int] = data.map{Int( $0) }
        
        let length = data.count
        
        for i in 0 ..< length {
            crc = ((crc & 0xFF) << 8) ^ crcTable[(((crc & 0xFF00) >> 8) ^  dataInt[i]) & 0xFF]
        }
        
        crc = crc & 0xFFFF
        return UInt16(crc)
    }
    
}
//
//  Utils.swift
//  蓝牙中心设备Swift
//
//  Created by JamesWang on 2018/8/17.
//
import Foundation
import UIKit
public class Utils {
    /// 将四个int8转为一个int32
    ///
    /// - Parameters:
    ///   - i1: 高位
    ///   - i2: <#i2 description#>
    ///   - i3: <#i3 description#>
    ///   - i4: 低位
    /// - Returns: <#return value description#>
    public static func convertInt(i1:Int,i2:Int,i3:Int,i4:Int) ->Int32{
        var val:Int
        val = ((i1 << 24))
        val = val | (i2 << 16)
        val = val | (i3 << 8)
        val = val | i4
        return (Int32)(val)
    }
    
    /// 将四个int按照高低位转换为Float
    ///
    /// - Parameters:
    ///   - i1: 高位
    ///   - i2:
    ///   - i3: <#i3 description#>
    ///   - i4: 低位
    /// - Returns: <#return value description#>
    public static func convertFloat(i1:UInt8,i2:UInt8,i3:UInt8,i4:UInt8) -> Float {
        var float32value:Float32 = 0
        let data:[UInt8] = [i4,i3,i2,i1]
        memcpy(&float32value, data, 4)
//        print("value = \(float32value)")
        return float32value
    }
    
    /// 将两个Int8转为一个int
    ///
    /// - Parameters:
    ///   - i1: 高位
    ///   - i2: 低位
    /// - Returns:
    public static func convertUShort(i1:UInt8,i2:UInt8) -> UInt16 {
        return UInt16((i1  << 8) | (i2 & 0x00ff))
    }
    /// 将两个Int8转为一个int
    ///
    /// - Parameters:
    ///   - i1: 高位
    ///   - i2: 低位
    /// - Returns:
    public static func convertShort(i1:UInt8,i2:UInt8) -> Int16 {
        return Int16((i1  << 8) | (i2 & 0x00ff))
    }
    // MARK: - Constants
    
    // This is used by the byteArrayToHexString() method
    private static let CHexLookup : [Character] =
        [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ]
    
    
    // Mark: - Public methods
    ///将一个数组转换为16进制的字符串 中间用空格分隔
    public static func byteArrayToHexString(_ byteArray : [UInt8]) -> String {
        var stringToReturn = ""
        for oneByte in byteArray {
            let asInt = Int(oneByte)
            stringToReturn.append(Utils.CHexLookup[asInt >> 4])
            stringToReturn.append(Utils.CHexLookup[asInt & 0x0f])
            stringToReturn.append(" ")
        }
        return stringToReturn
    } 
}

extension Int16 {
    func toBytes() -> Data {
        var data = Data()
        data.append((UInt8)(self >> 8 & 0x00ff))
        data.append((UInt8)(self & 0x00ff))
        return data
    }
}


extension UInt16{
    func toBytes() -> Data {
        var data = Data()
        data.append((UInt8)(self >> 8 & 0x00ff))
        data.append((UInt8)(self & 0x00ff))
        return data
    }
}
extension UInt32 {
    func toBytes() -> Data {
        var data = Data()
        data.append((UInt8)(self >> 24 & 0x00ff))
        data.append((UInt8)(self >> 16 & 0x00ff))
        data.append((UInt8)(self >> 8 & 0x00ff))
        data.append((UInt8)(self & 0x00ff))
        return data
    }
}
extension Data {    
    init<T>(from value: T) {
        var value = value
        self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
    }
    
    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.pointee }
    }
    
    init<T>(fromArray values: [T]) {
        var values = values
        self.init(buffer: UnsafeBufferPointer(start: &values, count: values.count))
    }
    
    func toArray<T>(type: T.Type) -> [T] {
        return self.withUnsafeBytes {
            [T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout<T>.stride))
        }
    }
    
    mutating func append(uint16value:UInt16) {
        self.append(uint16value.toBytes())
    }
    
    mutating func append(int16value:Int16) {
        self.append(int16value.toBytes())
    }
    
    mutating func append(uint32value:UInt32) {
        self.append(uint32value.toBytes())
    }
    mutating func append(float32value:Float32) {
        let data = Data(from:float32value)
        let temp:[UInt8] = data.toArray(type: UInt8.self)
        self.append(contentsOf:temp.reversed())
    }
    
    /// 从Data中读取一个UInt8数据
    ///
    /// - Returns: success 是否读取成功   uint8vale 读取到的值
    mutating func readUInt8() -> (success:Bool, uint8value:UInt8) {
        var uint8value:UInt8 = 0
        var success:Bool = false
        if(self.count < 1) {
            success = false
        }
        else {
            uint8value = self.first!
            success = true
            self.removeFirst()
        }
        return (success,uint8value)
    }
    
    mutating func subdata(in range: CountableClosedRange<Data.Index>) -> Data
    {
        return self.subdata(in: range.lowerBound..<range.upperBound + 1)
    }
    
    
    /// 从Data中读取一个UInt16数据
    ///
    /// - Returns: success 是否读取成功   uint16value 读取到的值
    mutating func readUInt16() -> (success:Bool, uint16value:UInt16) {
        var uint16value:UInt16 = 0
        var success:Bool = false
        if(self.count >= 2) {
            let bytes: [UInt8] = self.subdata(in: 0...1).toArray(type: UInt8.self)
            self.removeSubrange(0...1)
            let u0:UInt16 = UInt16(bytes[0])
            let u1:UInt16 = UInt16(bytes[1])
            uint16value = UInt16((u0  << 8) | (u1 & 0x00ff))
            success = true
        }
        return (success,uint16value)
    }
    
    /// 从Data中读取一个UInt32数据
    ///
    /// - Returns: success 是否读取成功   uint32value 读取到的值
    mutating func readUInt32() -> (success:Bool, uint32value:UInt32) {
        var uint32value:UInt32 = 0
        var success:Bool = false
        if(self.count >= 4) {
            let bytes: [UInt8] = self.subdata(in: 0...3).toArray(type: UInt8.self)
            self.removeSubrange(0...3)
            let u0:UInt32 = UInt32(bytes[0])
            let u1:UInt32 = UInt32(bytes[1])
            let u2:UInt32 = UInt32(bytes[2])
            let u3:UInt32 = UInt32(bytes[3])
            print("\(u0)\(u1)\(u2)\(u3)")
            uint32value = UInt32( (u0 << 24) | (u1 << 16) | (u2 << 8) | u3)
            success = true
        }
        return (success,uint32value)
    }
    
    /// 从Data中读取一个Float32数据
    ///
    /// - Returns: success 是否读取成功   float32value 读取到的值
    mutating func readFloat32() -> (success:Bool, float32value:Float32) {
        var float32value:Float32 = 0
        let success:Bool = false
        if(self.count >= 4) {
            let bytes: [UInt8] = self.subdata(in: 0...3).toArray(type: UInt8.self)
            self.removeSubrange(0...3)
            memcpy(&float32value, bytes, 4)
        }
        return (success,float32value)
    }
    
    mutating func hexEncodedString(options: HexEncodingOptions = []) -> String {
        let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16)
        var chars: [unichar] = []
        chars.reserveCapacity(2 * count)
        for byte in self {
            chars.append(hexDigits[Int(byte / 16)])
            chars.append(hexDigits[Int(byte % 16)])
        }
        return String(utf16CodeUnits: chars, count: chars.count)
    }
    
    
    struct HexEncodingOptions: OptionSet {
        let rawValue: Int
        static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
    }
}


func showAlert() {
        let alertController = UIAlertController(title: "系统提示",message: "请前往设置打开蓝牙", preferredStyle: UIAlertControllerStyle.alert)
        let cancelAction = UIAlertAction(title: "取消", style: UIAlertActionStyle.cancel, handler: nil)
        let okAction = UIAlertAction(title: "设置", style: UIAlertActionStyle.default,
                                handler: {
                                    action in
                                            let bluetoothurl = URL(string: UIApplicationOpenSettingsURLString)
                                            if let url = bluetoothurl, UIApplication.shared.canOpenURL(url)
                                            {
                                                UIApplication.shared.openURL(url)
                                            }
                                            else {
                                                print("打不开")
                                            }
                        })
        alertController.addAction(cancelAction)
        alertController.addAction(okAction)
        UIApplication.shared.windows[0].rootViewController?.present(alertController, animated: true, completion: nil)
    
    
}

备注:

因为之前没有接触过IOS开发,在进行蓝牙开发的时候,发现手机若在蓝牙设置中打开蓝牙,但是app启动后仍然会提示蓝牙状态处于未启动状态,如果是通过控制中心打开的蓝牙则不会有这个问题,网上找了很多资料,可能是ios的一个bug

猜你喜欢

转载自blog.csdn.net/weixin_37699212/article/details/82151955