先说一下我的目录结构(swift4.0)
1.目录结构
文件 | 内容 |
---|---|
Const | 定义ServiceUUID 和CharacteristicUUID |
ZLBleDocument | 使用方法 |
ZLBluetoothManagerDelegate | 自定义代理 |
ZLBluetoothManager | 核心文件,扫描,连接 |
ZLBluetoothConfig | 搜索产品配置文件 |
ZLPeripheral | 广播数据转化 |
ZLBluetoothMessage | 发送的蓝牙数据 |
ZLBluetoothCallBackMessage | 接受的蓝牙数据解析 |
ZLString + Extension | 16进制String和Data的转化 |
ZLData + Extension | 分类为取指定范围的数据 |
下面直接代码走起
2. Const.Swift
这个文件定义的是一些SeriveUUID和CharacteristicUUID,根据情况自己定义
let CUSTOM_SERVICE_UUID = CBUUID.init(string: "0000FF00-0000-1234-8000-00805F9B34FB")
let WRITE_CHARACTERISTIC_UUID = CBUUID.init(string: "0000FF01-0000-1234-8000-00805F9B34FB")
let NOTIFY_CHARACTERISTIC_UUID = CBUUID.init(string: "0000FF02-0000-1234-8000-00805F9B34FB")
//解释一下这个是做OTA的一个专用通道
let CUSTOM_BOOT_LOADER_SERVICE_UUID = CBUUID.init(string: "00060000-F8CE-11E4-ABF4-1234A5D5C51B")
let BOOT_LOADER_CHARACTERISTIC_UUID = CBUUID.init(string: "00060001-F8CE-11E4-ABF4-1234A5D5C51B")
3. ZLBleDocument.Swift(使用方法)
1.初始化
//设置扫描的产品码和代理,读写和升级的characteristic已经设置默认值,也可在参数中设置
manager = ZLBluetoothManager(config: ZLBluetoothConfig(scanProductCode: "7010"))
manager?.delegate = self
2.扫描
manager?.scan(progresshandle: { [weak self] (per) in
guard let _ = per?.peripheral else {
print("设备为空")
return
}
//这里可以直接将per(ZLPeripheral)中的数据拿出来展示UI
self?.lock.lock()
if self?.connectingModel == nil {
self?.connectPer(per: per!.peripheral!)
self?.connectingModel = model
}
self?.lock.unlock()
}) {[weak self] (perArr, error) in
//扫描结束回调
可能结果 :
nil,centralStateError //蓝牙状态错误
nil,busy //正在扫描
perArr,timeout //扫描的指定设备列表,超时
perArr,stop //连接成功,结束扫描,回调结果
print("\(String(describing: perArr)),error:\(String(describing: error))")
if error == .timeout {
}
if error == .centralStateError {
}
}
3.连接
manager?.connect(peripheral: per, handle: {[weak self] (per,progress, error) in
连接可能结果:
nil,.noConnect,.busy // 连接超时,没有搜索到产品
peripheral,.connect,nil // 连接上设备
peripheral,.discoverServices,nil // 发现服务
peripheral,.discoverCharacteristics,nil //发现读或者写特征
peripheral,.disconnect,nil // 断开连接(复杂逻辑可在后面代理设置)
if per != nil &&
error == nil &&
progress == .discoverCharacteristics {
}
})
4. ZLBluetoothConfig.Swift
class ZLBluetoothConfig {
var scanProductCode: String? //需要扫描产品名
var writeCharacteristic: CBUUID
var notifyCharacteristic: CBUUID
var bootCharacteristic: CBUUID
init(scanProductCode: String,
writeCharacteristic: CBUUID = WRITE_CHARACTERISTIC_UUID,
notifyCharacteristic: CBUUID = NOTIFY_CHARACTERISTIC_UUID,
bootCharacteristic: CBUUID = BOOT_LOADER_CHARACTERISTIC_UUID) {
self.scanProductCode = scanProductCode
self.writeCharacteristic = writeCharacteristic
self.notifyCharacteristic = notifyCharacteristic
self.bootCharacteristic = bootCharacteristic
}
}
5. ZLBluetoothManagerDelegate.Swift
@objc protocol ZLBluetoothManagerDelegate: NSObjectProtocol {
//接受普通通道的蓝牙信息
@objc func peripheral(_ peripheral: CBPeripheral, didUpdateValueForNotifyCharacteristic: Data?)
//断开连接
@objc optional
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
//接受OTA通道蓝牙消息
@objc optional
func peripheral(_ peripheral: CBPeripheral, didUpdateValueForBootCharacteristic: Data?)
}
自定义的代理方法,其中peripheral(_ peripheral: didUpdateValueForNotifyCharacteristic:)
和 peripheral(_ peripheral, didUpdateValueForBootCharacteristic)
是用于接受普通和OTA的蓝牙消息回调
centralManager(_ central: didDisconnectPeripheral peripheral: error:)
用于断开连接的处理,还可以通过连接进度来处理(见ZLBluetoothManager)
6.定义扫描和连接的状态
enum FKBleScanError: Error {
case timeout //超时
case busy //正在扫描
case stop //主动停止扫描
case centralStateError //蓝牙状态错误
}
enum FKBleConnectError: Error {
case timeout
case busy
}
enum FkBleConnectProgress {
case noConnect //超时,还未连接
case connect //已连接
case discoverServices //已发现服务
case discoverCharacteristics //一发现普通通道特征
case disconnect
}
7. ZLPeripheral.Swift
对广播的数据进行处理,数据为可选类型取到的广播数据内容为"optional
class ZLPeripheral: NSObject {
var peripheral: CBPeripheral?
var advertisementData: [String : Any]?
var RSSI: NSNumber?
var productName: String?
var dataStr: String?
class func zl_Peripheral(peripheral: CBPeripheral?,advertisementData: [String : Any]?,RSSI: NSNumber?) -> ZLPeripheral {
let zl_peripheral = ZLPeripheral()
zl_peripheral.peripheral = peripheral
zl_peripheral.advertisementData = advertisementData
zl_peripheral.RSSI = RSSI
zl_peripheral.productName = peripheral?.name
zl_peripheral.dataStr = String.init(format: "%@", advertisementData?["kCBAdvDataManufacturerData"] as? CVarArg ?? "")
.replacingOccurrences(of: " ", with: "")
.replacingOccurrences(of: "<", with: "")
.replacingOccurrences(of: ">", with: "")
return zl_peripheral
}
}
8.ZLBluetoothManager.Swift(核心类)
final class ZLBluetoothManager: NSObject {
typealias ScanProgressHandle = (ZLPeripheral?) -> Void
typealias ScanCompleteHandle = ([ZLPeripheral]?, FKBleScanError?) -> Void
typealias ConnectHandle = (CBPeripheral?,FkBleConnectProgress,FKBleConnectError?) -> Void
weak var delegate:ZLBluetoothManagerDelegate?
var writeCodeCharacteristic: CBCharacteristic?
var notifyCharacteristic: CBCharacteristic?
var bootLoaderCharacteristic: CBCharacteristic?
//当前连接的设备
var peripheral: CBPeripheral?
//断开连接的设备
var oldPeripheral: CBPeripheral?
var centralMgr:CBCentralManager!
//扫描指定产品的设备(有重复)
fileprivate var scanProgressHandle: ScanProgressHandle?
//扫描结束回调扫描到的指定产品列表和错误
fileprivate var scanCompleteHandle: ScanCompleteHandle?
//是否正在扫描
fileprivate var scanIsBusy: Bool = false
fileprivate var scanTime: Timer?
//扫描到的产品集合
var deviceArr = [ZLPeripheral]()
//包含设备蓝牙,进度,错误
fileprivate var connectHandle: ConnectHandle?
fileprivate var connectIsBusy: Bool = false
fileprivate var connectTime: Timer?
//配置文件(需要搜索的产品)
fileprivate var config: ZLBluetoothConfig?
init(config: ZLBluetoothConfig) {
super.init()
self.centralMgr = CBCentralManager.init(delegate: self, queue: nil)
self.config = config
}
//默认扫描30.0s,超时停止扫描,Error = timeout
internal func scan(timeout: TimeInterval = 30.0,progresshandle: ScanProgressHandle?,comleteHandle: ScanCompleteHandle?) {
guard scanIsBusy == false else {
comleteHandle?(nil,.busy)
return
}
guard self.centralMgr.state == .poweredOn else {
comleteHandle?(nil,.centralStateError)
return
}
scanProgressHandle = nil
scanCompleteHandle = nil
if progresshandle != nil {
scanProgressHandle = progresshandle
}
if comleteHandle != nil {
scanCompleteHandle = comleteHandle
}
if (self.centralMgr.state == .poweredOn) {
self.centralMgr.scanForPeripherals(withServices: nil,options:[CBCentralManagerScanOptionAllowDuplicatesKey: false])
scanIsBusy = true
scanTime = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(scanTimeout), userInfo: nil, repeats: false)
return
}
print("打开蓝牙失败:%d",self.centralMgr.state.rawValue)
}
//连接时重置属性
fileprivate func restoreState() {
self.peripheral = nil
self.notifyCharacteristic = nil
self.writeCodeCharacteristic = nil
self.bootLoaderCharacteristic = nil
}
//扫描超时
@objc fileprivate func scanTimeout() {
if scanTime != nil {
scanCompleteHandle?(deviceArr,.timeout)
}
invalidateScanState()
}
//主动停止扫描(连接成功后自动停止)
func stopScan() {
scanCompleteHandle?(deviceArr,.stop)
invalidateScanState()
}
fileprivate func invalidateScanState() {
scanTime?.invalidate()
scanTime = nil
scanIsBusy = false
deviceArr = [ZLPeripheral]()
self.centralMgr.stopScan()
}
//连接设备
internal func connect(peripheral:CBPeripheral?,timeout: TimeInterval = 5.0,handle: ConnectHandle?) {
guard connectIsBusy == false else {
connectHandle?(nil,.noConnect,.busy)
return
}
connectHandle = nil
if let per = peripheral {
connectTime = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(disConnect), userInfo: nil, repeats: false)
connectHandle = handle
self.centralMgr.connect(per, options: nil)
connectIsBusy = true
}
}
@objc internal func disConnect() {
if connectTime != nil {
connectHandle?(nil,.noConnect,.timeout)
}
if let per = self.peripheral {
self.centralMgr.cancelPeripheralConnection(per)
self.peripheral = nil
}
connectTime?.invalidate()
connectTime = nil
connectIsBusy = false
}
//重新连接,如果没有特殊逻辑,重连的处理同连接
internal func reconnect(handle: ConnectHandle? = nil) {
if #available(iOS 9.0, *) {
if oldPeripheral != nil &&
(oldPeripheral!.state == .disconnected || oldPeripheral!.state == .disconnecting) {
self.connect(peripheral: oldPeripheral, handle: handle != nil ? handle : connectHandle)
}
} else {
if oldPeripheral != nil &&
oldPeripheral!.state == .disconnected {
self.connect(peripheral: oldPeripheral, handle: handle != nil ? handle : connectHandle)
}
}
}
internal func removeData() {
deviceArr.removeAll()
}
//向设备写入数据
internal func write(data: Data) {
guard let _ = peripheral else {
print("未找到设备")
return
}
guard peripheral?.state == .connected && writeCodeCharacteristic != nil else {
print("设备蓝牙未连接")
return
}
peripheral?.writeValue(data, for:writeCodeCharacteristic! , type: .withResponse)
}
}
extension ZLBluetoothManager:CBCentralManagerDelegate,CBPeripheralDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("检查蓝牙状态")
switch central.state {
case .poweredOn:
print("蓝牙打开成功")
default:
print("蓝牙打开失败")
scanCompleteHandle?(nil,.centralStateError)
if (self.peripheral != nil) {
self.centralMgr .cancelPeripheralConnection(self.peripheral!)
}
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// print("name = \(peripheral.name ?? ""),identify: \(peripheral.identifier),RSSI = \(RSSI)")
guard config?.scanProductCode != nil else {
print("\nError: 产品编号为空\n")
return
}
if (peripheral.name?.contains((config?.scanProductCode)!)) == true {
print("\n\n\n---------------\n\(String(describing: config?.scanProductCode)) = \(advertisementData)\n-------------\n\n\n")
let zl_peripheral = ZLPeripheral.zl_Peripheral(peripheral: peripheral, advertisementData: advertisementData, RSSI: RSSI);
//返回扫描到的指定产品
scanProgressHandle?(zl_peripheral)
var container = false
for z_perpheral in deviceArr {
if (z_perpheral.peripheral?.isEqual(peripheral))! {
container = true
}
}
if !container {
deviceArr.append(zl_peripheral)
}
let str = advertisementData["kCBAdvDataManufacturerData"].debugDescription
print(str.replacingOccurrences(of: " ", with: ""))
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("已连接%@",peripheral)
self.restoreState()
self.peripheral = peripheral
self.peripheral?.delegate = self
self.peripheral?.discoverServices(nil)
stopScan()
oldPeripheral = nil;
connectIsBusy = false
connectTime?.invalidate()
connectTime = nil
connectHandle?(peripheral,.connect,nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("发现服务")
if error != nil {
print(error ?? "didDiscoverServicesError")
return
}
if let services = peripheral.services {
for service in services {
self.peripheral?.discoverCharacteristics(nil, for: service)
}
connectHandle?(peripheral,.discoverServices,nil)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("发现特征")
if (error != nil) {
print(error ?? "discoverError")
return
}
guard let _ = service.characteristics else {
print("没有发现特征")
return
}
for characteristic in service.characteristics! {
if characteristic.uuid == config?.writeCharacteristic {
writeCodeCharacteristic = characteristic
}
if characteristic.uuid == config?.notifyCharacteristic {
notifyCharacteristic = characteristic
self.peripheral?.readValue(for: characteristic)
self.peripheral?.setNotifyValue(true, for: characteristic)
print("监听:%@",characteristic)
}
if characteristic.uuid == config?.writeCharacteristic ||
characteristic.uuid == config?.notifyCharacteristic {
connectHandle?(peripheral,.discoverCharacteristics,nil)
}
if characteristic.uuid == config?.bootCharacteristic {
bootLoaderCharacteristic = characteristic
self.peripheral?.readValue(for: characteristic)
self.peripheral?.setNotifyValue(true, for: characteristic)
print("监听:%@",characteristic)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard error == nil else {
print("didUPdateValueError = \(error!)")
return
}
if characteristic.uuid == config?.notifyCharacteristic {
print("收到监听的设备值改变发送的通知\(String(describing: characteristic.value))")
delegate?.peripheral(peripheral, didUpdateValueForNotifyCharacteristic: characteristic.value)
return
}
if characteristic.uuid == config?.bootCharacteristic {
print("设备升级:\(String(describing: characteristic.value?.description))")
delegate?.peripheral?(peripheral, didUpdateValueForBootCharacteristic: characteristic.value)
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if error == nil {
print("写入成功")
} else {
print("写入失败")
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("设备断开连接")
oldPeripheral = peripheral;
self.restoreState()
self.peripheral = nil
connectHandle?(peripheral,.disconnect,nil)
delegate?.centralManager?(central, didDisconnectPeripheral: peripheral, error: error)
}
}
9 ZLBluetoothMessage和 ZLBluetoothCallBackMessage
分别为发送的蓝牙数据和蓝牙数据解析,为了减轻Controller的工作
Example:
发送数据:
//回复稳定数据
public func z_steadyBodyDataMessage() ->Data {
let start = "5ad2"
let retain = "00000000000000000000000000000000"
let str = start + retain
var data = Data()
//+~ 为空返回自身,反之拼接
data +~ str.z_hexStringToData()
addCheckSum(data: &data)
data +~ "aa".z_hexStringToData()
print("steadyDataMessage:\(data.description)")
return data
}
校验位计算规则:从第1位开始到校验位前一个数据结束,做^运算:
//MARK:校验位
public func addCheckSum(data: inout Data) {
var out = UInt8.init(0x00)
let array = data.withUnsafeBytes {
[UInt8](UnsafeBufferPointer(start: $0, count: data.count))
}
print(array.count)
for i in 1..
其中 +~为自定义运算符
precedencegroup dataAssignedPredence {
associativity: right
higherThan: DefaultPrecedence
}
infix operator +~ : dataAssignedPredence
public func +~(x:inout Data,y: Data?) {
guard let _ = y else {
return
}
x = x + y!
}
10. ZLString + Extension.Swift
- 十六进制Str转十进制Str
- 十六进制Str转Data
extension String {
public func z_hexStringToString() -> String {
var str = self.replacingOccurrences(of: " ", with: "")
if str.characters.count % 2 != 0 {
print("解析数据为单数")
}
str = str.uppercased()
var sum = 0
for i in str.utf8 {
sum = sum * 16
if i >= 48 && i <= 57 {
sum += Int(i) - 48
}
if i >= 65 {
sum += Int(i) - 55
}
}
return String(sum)
}
public func z_hexStringToData() ->Data? {
var value = self
var data:Data = Data()
if value.characters.count % 2 != 0 {
print("Error: 解析数据为单数")
return nil
}
for i in 0..
11.ZLData + EXtension.Swift
- 取指定字节的字符串
- 取指定范围的字符串
extension Data {
public func z_strWithIndex(_ index: Int) ->String? {
guard index < self.count else {
return nil
}
let array = self.withUnsafeBytes {
[UInt8](UnsafeBufferPointer(start: $0, count: self.count))
}
guard array.count > 0 else {
return nil
}
let result = String(format: "%02x", array[index])
return result
}
public func z_strWithRange(location: Int,length: Int) -> String? {
var str: String = ""
for i in location..