之前一直做的是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