swift 蓝牙连接
项目简介
最近公司要用到便携式蓝牙打印机进行打印
打印机使用的ECS/POS指令集
ios使用的BLE方案,安卓则是因为设备的原因只能选择普通蓝牙的连接方案
BLE蓝牙相关的概念性东西我这里就不说了 大家可以自己去搜索下
有个mac的蓝牙开发工具LightBlue,很好用,mac app store 自己下就好了,这个很方便你理解BLE的蓝牙相关
device(central/peripheral)->services->characteristics
这里可以看到,有一个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
这个是真实的输出方法,这个输出方法使用了了之前的peripheral
和characteristic
这里之所以要持有所有'中间'产物的引用是因为之前用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,还有一个有回应的是,这里就看蓝牙设备本身如何设定的了
这里读取暂时没涉及到,有需要的同学自己研究下吧
最后祝大家都能顺利的完成自己的蓝牙连接!!
网友评论