swift 蓝牙连接

项目简介

最近公司要用到便携式蓝牙打印机进行打印
打印机使用的 ECS/POS 指令集
ios 使用的 BLE 方案,安卓则是因为设备的原因只能选择普通蓝牙的连接方案
BLE 蓝牙相关的概念性东西我这里就不说了 大家可以自己去搜索下
有个 mac 的蓝牙开发工具 LightBlue,很好用,mac app store 自己下就好了,这个很方便你理解 BLE 的蓝牙相关 device(central/peripheral)->services->characteristics

屏幕快照 2017-09-08 下午3.39.04.png

这里可以看到,有一个 mobike,有兴趣的同学可以研究下 mobike 的蓝牙连接 ☺ 说笑一下,人家肯定有安全性校验的

语言选型

最近在学习 ios 开发,因为我是 android 出身,学习 ios 开发的时候 swift3 已经出现很久了,所以我这里使用的 swift 进行开发

坑 1

最初我使用了厂家提供的 ios sdk 进行开发,其中封装了很多常用的方法,让我自己以为很简单就能完成,但是事实上是我太天真了,首先厂家提供的是.a 的库,只有一个.h 文件暴露在外,我的项目是纯 swift 项目,这就不可避免的使用到了 swift 到 oc 的桥接

坑 2

满以为桥接完了调 SDK 方法就行,谁知道调用的时候根本就没反应,没办法,只能摸石头过河进行开发了,最初使用的是 oc 的 corebluetooth 方案,因为实在是没找到 swift 的相关说法,baidu 没搜到,没办法,oc 毕竟也算是入门了,直接开干了

坑 3

开发完 oc 的连接 demo,强迫症发作,决定一定要用纯 swift 开发,毕竟我们还是要跟随时代脚步的嘛

找文档

都说苹果的官方文档写的很好,那么我就上去看看吧,这里要吐槽一点,文档的方法,类描述确实很不错,看起来很清晰,但是但是..怎么没有告诉我 import 什么

UIKit 里不包含 Bluetooth 相关的类,而官方中将这个定义在System体系的 Core Bluetooth 中

使用

import CoreBluetooth

这样CBCentralManager终于可以用了

正式开发

折腾了半天,终于可以开始开发了…

macos 模拟器蓝牙 central

这里要吐槽下公司,没有 ios 测试机,我自己又是安卓手机,没办法,这里有一招,建一个 macos 的项目,将UIKit换成Cocoa,或者Foundation,然后其他语法中 macos 和 ios 的蓝牙部分代码几乎一样,这样就能连接上打印机了,我这里又是模块开发,将数据部分的代码 copy 一份到 macos 的 demo 上,就能模拟真机的 mac 了
上面说几乎一样的原因是,cm.isScanning在 mac 开发中用不了,ios 中可以

当时记得苹果说 simulator 可以用 mac 的蓝牙开发,结果短短的一个版本以后就干掉了相关功能,真是狗

帮助类的代码


//
//  BluetoothHelper.swift
//  SwiftBluetoothScanDemo1
//
//  Created by caijinglong on 2017/9/9.
//

import Foundation
import CoreBluetooth

protocol BluetoothHelperDelegate {

    func bluetoothHelperIndex()->Int

    func bluetoothHelperNotifyConnected(isConnected:Bool)

    func bluetoothHelperAutoStopScan()
}

class BluetoothHelper :NSObject,CBCentralManagerDelegate,CBPeripheralDelegate{

    static let shared = BluetoothHelper()

    private var cm:CBCentralManager! = nil
    private var peripheral: CBPeripheral! = nil
    private var service:CBService! = nil

    private var characteristic:CBCharacteristic! = nil

    private var delegateDict = Dictionary<Int,BluetoothHelperDelegate>()

    public func registerDelegate(delegate:BluetoothHelperDelegate){
        delegateDict[delegate.bluetoothHelperIndex()] = delegate
    }

    public func unregisterDelegate(delegate:BluetoothHelperDelegate){
        delegateDict.removeValue(forKey: delegate.bluetoothHelperIndex())
    }

    private override init(){
        super.init()
        self.cm = CBCentralManager(delegate: self, queue: nil)
    }

    /// 被连接的打印机的名字
    var connectDeviceName:String? = ""

    /// 是否连接了打印机
    var isConnected:Bool = false{
        didSet{
            if(!isConnected){
                peripheral = nil
                service = nil
                characteristic = nil
                connectDeviceName = nil

                delegateDict.forEach({ (key,delegate) in

                })
            }else{
                connectDeviceName = self.name
            }
        }
    }

    /// 蓝牙开关是否打开
    var btOpen = false
    private var name = "QSPrinter"

    // 后面是代理方法
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        NSLog("状态变化")
        if(central.state.rawValue == 5){
            btOpen = true
        }else{
            btOpen = false
            isConnected = false
        }
    }

    /// 开始扫描设备
    func startScan(name:String){
        self.name = name
        self.cm.stopScan()
        self.cm.scanForPeripherals(withServices: nil, options: nil)
        runDelay(5) {
            self.delegateDict.forEach({ (_,delegate) in
                self.cm.stopScan()
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

    /// 停止扫描设备
    func stopScan(){
        self.cm.stopScan()
    }

    /// 关闭连接设备
    func disconnect(){
        if(peripheral != nil){
            self.cm.cancelPeripheralConnection(peripheral)
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        NSLog("\(String(describing: peripheral.name)) is discovered")
        if(peripheral.name?.uppercased() == name.uppercased()){
            self.peripheral = peripheral
            peripheral.delegate = self
            cm.connect(peripheral)
            cm.stopScan()
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog("\(String(describing: peripheral.name)) 连接成功")
        let uuid = CBUUID(string: "18F0")
        peripheral.discoverServices([uuid])
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NSLog("\(String(describing: peripheral.name)) 连接断开")
        isConnected = false
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let service = peripheral.services?[0]{
            let uuid = CBUUID(string: "2AF1")
            peripheral.discoverCharacteristics([uuid], for: service)
            self.service = service
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristic = service.characteristics?[0]{
            NSLog("characteristic is prepared")
            isConnected = true
            self.characteristic = characteristic
        }
    }

    /// 输出字符串
    func writeText(text:String)throws{
        let enc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue))

        if let data = text.data(using: String.Encoding(rawValue: enc)){
            do {
               try self.writeData(data: data)
            } catch  {
                throw error
            }
        }
    }


    private var datas = [Data]()

    /// 写入数据
    func writeData(data:Data) throws {
        if(isConnected){
            datas.append(data)
        }else{
            throw BtError.NoConnectError
        }
    }

    /// 真实的打印方法
    func print(){
        for index in 0 ... datas.count - 1  {
            let item = datas[index]
            runDelay(0.02 * Double(index), {
                self.peripheral.writeValue(item, for: self.characteristic, type: CBCharacteristicWriteType.withoutResponse)
            })
        }
        self.datas.removeAll()
    }

}

enum BtError :Error{
    case NoConnectError
}

分析下代码 这里使用单例的方案管理连接,实际上 BLE 支持同时连接多个外设,我这里是因为目前没有这样的需求,所以考虑使用单例的模式,看官请根据自己的需求来

centralManagerDidUpdateState这个代理方法很重要,是唯一一个必须实现的方法,用于监听蓝牙的状态,是一个 Int 类型的枚举值,这里因为 ios10 有一个过期相关的提示,替换了 state 相关的类由CBCentralManagerState替换到CBManagerState,值没有变化,就是由 oc 的枚举方式替换到了 swift 的枚举,这里我直接使用 5 来进行判断,ios11 也没看见修改这个数值,短时间内直接用 5 就行,后续有 bug 再说 这里用一个 property 来储存蓝牙状态

startScan其中是开始扫描的方法

    func startScan(name:String){
        self.name = name
        self.cm.stopScan()
        self.cm.scanForPeripherals(withServices: nil, options: nil)
        runDelay(5) {
            self.delegateDict.forEach({ (_,delegate) in
                self.cm.stopScan()
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

先停止扫描,然后开始扫描,记录一下 name,后续会用到,5 秒后停止扫描,并代理通知

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        NSLog("\(String(describing: peripheral.name)) is discovered")
        if(peripheral.name?.uppercased() == name.uppercased()){
            self.peripheral = peripheral
            peripheral.delegate = self
            cm.connect(peripheral)
            cm.stopScan()
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog("\(String(describing: peripheral.name)) 连接成功")
        let uuid = CBUUID(string: "18F0")
        peripheral.discoverServices([uuid])
    }

这两个方法,第一个是扫描到了设备,这里我忽视大小写进行匹配,然后如果名字匹配则调用cm.connect(peripheral)进行连接,并且停止扫描
第二个方法是连接成功,这里 18F0 是 service 的名称,就是扫描 UUID 为 18F0 的 services

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let service = peripheral.services?[0]{
            let uuid = CBUUID(string: "2AF1")
            peripheral.discoverCharacteristics([uuid], for: service)
            self.service = service
        }
    }

这里是在扫描到 services 后的代理,然后扫描 uuid 为2AF1的 Characteristics 这里 service 保持引用

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristic = service.characteristics?[0]{
            NSLog("characteristic is prepared")
            isConnected = true
            self.characteristic = characteristic
        }
    }

这里是扫描到 Characteristics 后的操作,这里我记录了一个连接状态 和 characteristic 的引用

self.peripheral.writeValue(item, for: self.characteristic, type: CBCharacteristicWriteType.withoutResponse)//item是Data

这个是真实的输出方法,这个输出方法使用了了之前的peripheralcharacteristic 这里之所以要持有所有’中间’产物的引用是因为之前用 oc 写这个代码的时候因为没有持有 peripheral 的引用报错,导致代理获取不到数据

详细分析写数据

这里我使用了一个方案来写

   /// 输出字符串
    func writeText(text:String)throws{
        let enc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue))

        if let data = text.data(using: String.Encoding(rawValue: enc)){
            do {
               try self.writeData(data: data)
            } catch  {
                throw error
            }
        }
    }


    private var datas = [Data]()

    /// 写入数据
    func writeData(data:Data) throws {
        if(isConnected){
            datas.append(data)
        }else{
            throw BtError.NoConnectError
        }
    }

    /// 真实的打印方法
    func print(){
        for index in 0 ... datas.count - 1  {
            let item = datas[index]
            runDelay(0.02 * Double(index), {
                self.peripheral.writeValue(item, for: self.characteristic, type: CBCharacteristicWriteType.withoutResponse)
            })
        }
        self.datas.removeAll()
    }
func runDelay(_ delay:TimeInterval,_ block:@escaping () -> ()){
    let queue = DispatchQueue.main

    let delayTime = DispatchTime.now() + delay

    queue.asyncAfter(deadline: delayTime) {
        block()
    }
}

这里的思路大概是: 首先一个容器用于储存顺序存入的数据,然后在调用print的时候将所有数据进行输出,并且延迟一定的时间,每个输出时间间隔 0.02s
之所以这么做的原因是:我当时直接调用writeValue方法写数据,没有间隔时间,发现会出现样式错误,输出顺序错误的情况发生
这个时候我凭感觉认为是发生了writeValue和实际通讯的到达顺序不一致的问题,我查了下,BLE 主打的是低延迟,但是对应的数据通讯的数据量就有了限制,所以采用这个方案

当然我也试过使用一个 Data 储存所有字节数据的方案,发现打印机无法打印,具体原因没有深究

打印相关

编码问题

一般的蓝牙打印机中文使用的是 GBK 编码,ios 中是 GB_18030_2000,而 ios 默认是 utf8 编码,所以这里需要显示指定

关于二维码

打印二维码使用的是 ESC/POS 的指令集,这个在指令集的说明文档中可以找到
这里的 moduleSize 是二维码的大小,具体参数可以参考说明文档,一般对接的打印机厂商都会提供
一般的打印机中文都是 GBK 编码的,而扫码一般是 UTF8 编码,这个编码转换很麻烦,所以尽量不要出现中文

打印机发送指令

//
//  PrinterHelper.swift
//  SwiftBluetoothScanDemo1
//
//  Created by Caijinglong on 2017/9/11.
//

import Foundation

/// printer helper
///
/// single instance
class PrinterHelper{

    static var shared:PrinterHelper = PrinterHelper()

    var helper : BluetoothHelper!

    //    var devices = [Printer]()

    private init(){
        helper = BluetoothHelper.shared
    }

    func registerDelegate(delegate: BluetoothHelperDelegate){
        helper.registerDelegate(delegate: delegate)
    }

    func unregisterDelegate(delegate: BluetoothHelperDelegate){
        helper.unregisterDelegate(delegate: delegate)
    }

    var index = 0

    let DIVIDER = "-----------------------------------------------"
    let ESC: Byte = 27//换码
    let FS: Byte = 28//文本分隔符
    let GS: Byte = 29//组分隔符
    let DLE: Byte = 16//数据连接换码
    let EOT: Byte = 4//传输结束
    let ENQ: Byte = 5//询问字符
    let SP: Byte = 32//空格
    let HT: Byte = 9//横向列表
    let LF: Byte = 10//打印并换行(水平定位)
    let CR: Byte = 13//归位键
    let FF: Byte = 12//走纸控制(打印并回到标准模式(在页模式下)
    let CAN: Byte = 24//作废(页模式下取消打印数据 )

    func conn(deviceName:String){
        helper.startScan(name: deviceName)
    }

    func disconnect(){
        helper.disconnect()
    }

    func sendMsg(msg:String) -> Self{
        try? helper.writeText(text: msg)
        return self
    }

    func sendBytes(bytes:[Byte]) -> Self{
        try? helper.writeData(data: Data.bytesArray(byteArray: bytes))
        return self
    }

    func sendHex(int:Int) -> Self {
        return self.sendHexs(hexInt: int)
    }

    func sendHexs(hexInt ints:Int...) -> Self{
        var data = Data()
        ints.forEach { (int) in
            data.append(UInt8(int))
        }
        try? helper.writeData(data: data)
        return self
    }

    func sendBytes(bytes:Byte...) -> Self{
        return sendBytes(bytes: bytes)
    }

    func alignLeft()-> Self{
        return sendBytes(bytes: ESC,97,0)
    }

    func alignCenter() -> Self {
        return sendBytes(bytes: ESC,97,1)
    }

    func alignRight() -> Self{
        return sendBytes(bytes: ESC,97,2)
    }

    func printDivider() -> Self {
        return sendMsg(msg: DIVIDER)
    }


    func startPrint(){
        helper.print()
    }

    func setFontSize(size:Int)  -> Self{
        var realSize: Byte = 0

        if(size <= 7){
            realSize = Byte(size * 17)
        }

        var result = [Byte]()
        result.append(0x1D)
        result.append(0x21)
        result.append(realSize)
        print("size = \(size)  realSize = \(realSize)")
        return sendBytes(bytes: result)
    }

    func newLine(lines:Int = 1) -> Self{
        for _ in 0...lines - 1{
            _ = sendHex(int: 0x0A)
        }
        return self
    }

    /**
     * 选择加粗模式

     * @return
     */
    func boldOn() -> Self {
        var result = [Byte]()
        result.append(ESC)
        result.append(69)
        result.append(0xF)
        return sendBytes(bytes: result)
    }


    /**
     * 取消加粗模式

     * @return
     */
    func boldOff() -> Self {
        var result = [Byte]()
        result.append(ESC)
        result.append(69)
        result.append(0)
        return sendBytes(bytes: result)
    }

    func subTitle(_ title:String) -> Self{
        return
            self.newLine()
                .setFontSize(size: 1)
                .boldOn()
                .alignCenter()
                .sendMsg(msg: title)
                .setFontSize(size: 0)
                .boldOff()
    }

    func sendQrcode(qrcode:String) -> Self{
        let moduleSize:Byte = 8
        var list = [Byte]()

        if let data = Data.gbkData(text: qrcode){

            //打印二维码矩阵
            list.append(0x1D)// init
            list.append(40) // adjust height of barcode
            list.append(107)// adjust height of barcode
            list.append(Byte(data.count + 3)) // pl
            list.append(0) // ph
            list.append(49) // cn
            list.append(80) // fn
            list.append(48) //

            data.forEach({ (char) in
                list.append(char)
            })

            list.append(0x1D)
            list.append(40)// list.append("(k")
            list.append(107)// list.append("(k")
            list.append(3)
            list.append(0)
            list.append(49)
            list.append(69)
            list.append(48)

            list.append(0x1D)
            list.append(40)// list.append("(k")
            list.append(107)// list.append("(k")
            list.append(3)
            list.append(0)
            list.append(49)
            list.append(67)
            list.append(moduleSize)

            list.append(0x1D)
            list.append(40)// list.append("(k")
            list.append(107)// list.append("(k")
            list.append(3) // pl
            list.append(0) // ph
            list.append(49) // cn
            list.append(81) // fn
            list.append(48) // m
        }

        return
            alignCenter()
            .sendBytes(bytes: list)
    }
}

蓝牙连接的类

//
//  BluetoothHelper.swift
//  SwiftBluetoothScanDemo1
//
//  Created by caijinglong on 2017/9/9.
//  Copyright © 2017 sxw. All rights reserved.
//

import Foundation
import CoreBluetooth

protocol BluetoothHelperDelegate:NSObjectProtocol {

    func bluetoothHelperIndex()->Int

    func bluetoothHelperNotifyConnected(isConnected:Bool)

    func bluetoothHelperAutoStopScan()

    func bluetoothHelperFindDevices(name:String)
}

extension BluetoothHelperDelegate{
    func bluetoothHelperFindDevices(name:String){
    }
}

protocol BluetoothHelperScanDeviceDelegate {
    func bluetoothScan(peripheral: CBPeripheral)

    func bluetoothHelperIndex()->Int

    func bluetoothConnected(name:String)

    func bluetoothDisconnect(name:String)
}

extension BluetoothHelperScanDeviceDelegate{
    func bluetoothConnected(name:String){}

    func bluetoothDisconnect(name:String){}
}

class BluetoothHelper :NSObject,CBCentralManagerDelegate,CBPeripheralDelegate{

    static let shared = BluetoothHelper()

    private var cm:CBCentralManager! = nil
    private var peripheral: CBPeripheral! = nil
    private var service:CBService! = nil

    private var characteristic:CBCharacteristic! = nil

    private var delegateDict = Dictionary<Int,BluetoothHelperDelegate>()

    private var scanDelegateDict = Dictionary<Int,BluetoothHelperScanDeviceDelegate>()

    public func registerDelegate(delegate:BluetoothHelperDelegate){
        delegateDict[delegate.bluetoothHelperIndex()] = delegate
    }

    public func unregisterDelegate(delegate:BluetoothHelperDelegate){
        delegateDict.removeValue(forKey: delegate.bluetoothHelperIndex())
    }

    public func registerScanDelegate(delegate:BluetoothHelperScanDeviceDelegate){
        scanDelegateDict[delegate.bluetoothHelperIndex()] = delegate
    }

    public func unregisterScanDelegate(delegate:BluetoothHelperScanDeviceDelegate){
        delegateDict.removeValue(forKey: delegate.bluetoothHelperIndex())
    }

    private override init(){
        super.init()
        self.cm = CBCentralManager(delegate: self, queue: nil)
    }

    /// 被连接的打印机的名字
    var connectDeviceName:String? = ""

    /// 是否连接了打印机
    var isConnected:Bool = false{
        didSet{
            if(!isConnected){
                peripheral = nil
                service = nil
                characteristic = nil

                delegateDict.forEach({ (key,delegate) in
                    delegate.bluetoothHelperNotifyConnected(isConnected: false)
                })
                connectDeviceName = nil
            }else{
                connectDeviceName = self.name

                delegateDict.forEach({ (key,delegate) in
                    delegate.bluetoothHelperNotifyConnected(isConnected: true)
                })
            }
        }
    }

    /// 蓝牙开关是否打开
    var btOpen = false
    private var name = "QSPrinter"

    // 后面是代理方法
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        NSLog("状态变化: state = \(central.state.rawValue)")
        if(central.state.rawValue == 5){
            btOpen = true
        }else{
            btOpen = false
            isConnected = false
        }
    }

    /// 仅扫描
    func startScan(){
        self.name = ""
        self.cm.stopScan()
        self.cm.scanForPeripherals(withServices: nil, options: nil)
        runDelay(5) {
            self.delegateDict.forEach({ (_,delegate) in
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

    /// 开始扫描设备
    func startScan(name:String){
        self.name = name
        self.cm.stopScan()
        self.cm.scanForPeripherals(withServices: nil, options: nil)
        runDelay(5) {
            self.delegateDict.forEach({ (_,delegate) in
                self.cm.stopScan()
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

    /// 停止扫描设备
    func stopScan(){
        self.cm.stopScan()
    }

    /// 关闭连接设备
    func disconnect(){
        if(peripheral != nil){
            self.cm.cancelPeripheralConnection(peripheral)
        }
    }

    /// 扫描到设备
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        NSLog("\(String(describing: peripheral.name)) is discovered")
        if(peripheral.name == nil){
            return
        }

        scanDelegateDict.forEach { (_,delegate) in
            delegate.bluetoothScan(peripheral: peripheral)
        }


        if(self.name.isEmpty){
            return
        }

        if(peripheral.name?.uppercased() == name.uppercased()){
            self.connect(peripheral: peripheral)
        }
    }

    /// 连接peripheral
    func connect(peripheral:CBPeripheral){
        self.peripheral = peripheral
        peripheral.delegate = self
        cm.connect(peripheral)
        cm.stopScan()
    }

    /// 连接成功后
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog("\(String(describing: peripheral.name)) 连接成功")
        let uuid = CBUUID(string: "18F0")
        peripheral.discoverServices([uuid])

        scanDelegateDict.forEach { (_,delegate) in
            delegate.bluetoothConnected(name: name)
        }
    }

    /// 断开连接后
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NSLog("\(String(describing: peripheral.name)) 连接断开")
        scanDelegateDict.forEach { (_,delegate) in
            delegate.bluetoothDisconnect(name: name)
        }
        isConnected = false
    }

    /// 扫描设备的services
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let service = peripheral.services?[0]{
            let uuid = CBUUID(string: "2AF1")
            peripheral.discoverCharacteristics([uuid], for: service)
            self.service = service
        }
    }

    /// 扫描service的characteristics
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristic = service.characteristics?[0]{
            NSLog("characteristic is prepared")
            isConnected = true
            self.characteristic = characteristic
        }
    }

    /// 输出字符串
    func writeText(text:String)throws{
        let enc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue))

        if let data = text.data(using: String.Encoding(rawValue: enc)){
            do {
               try self.writeData(data: data)
            } catch  {
                throw error
            }
        }
    }

    /// 输出二进制
    func writeBytes(){

    }

    private var lock = NSLock()
    private var isWritering = false

    private var tempData = Data()

    private var datas = [Data]()

    /// 写入数据
    func writeData(data:Data) throws {
        if(isConnected){
//            lock.lock(before: Date())
            tempData.append(data)
            datas.append(data)
        }else{
            throw BtError.NoConnectError
        }
    }

    /// 真实的打印方法
    func print(){
//        NSLog("printdata : \(tempData)")
        for index in 0 ... datas.count - 1  {
            let item = datas[index]
            runDelay(0.02 * Double(index), {
                self.peripheral.writeValue(item, for: self.characteristic, type: CBCharacteristicWriteType.withoutResponse)
            })
        }
        self.datas.removeAll()
    }

    private func _realWriterData(data:Data) {
        if(isConnected){
            NSLog("real write data : \(data)")
            self.peripheral.writeValue(data, for: self.characteristic, type: CBCharacteristicWriteType.withoutResponse)
        }else{

        }
    }
}

enum BtError :Error{
    case NoConnectError
}

## 后记 这里的代码是我测试项目中使用的,因为是我独立开发,所以有的代码比较乱,敬请见谅

蓝牙打印机连接本身不算什么高深的操作,只是其中的回调比较复杂,看起来麻烦,这里我也没讲什么概念性的东西,主要就是讲解下代码和实现步骤啥的 搞清楚了蓝牙外设提供的服务有什么,如何连接,另外需要注意的是 CBCharacteristicWriteType.withoutResponse,还有一个有回应的是,这里就看蓝牙设备本身如何设定的了
这里读取暂时没涉及到,有需要的同学自己研究下吧
最后祝大家都能顺利的完成自己的蓝牙连接!!