1. 程式人生 > >IOS--swift BLE藍芽通訊管理(多裝置)

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)")