分阶段了解:
- 扫描
- 连接
- 发现服务和特征,写入或读取数据
扫描类
BTScanner
这个类是对CBCentralManager类中scanForPeripherals方法的封装.而扫描到的外设会通过CBCentralManagerDelegate中的代理方法centralManager(_: didDiscover: advertisementData: rssi:)实时的回调出来
首先说一下这两个方法的用途及参数介绍:
/*
* withServices
1.指定扫描外设的ServiceUUID,一般是和外设开发同事协商好的,所有设备都共用这一个,是由外设在广播时初始化传入的,并告知中心设备
2.这里也可传nil,表示扫描附近所有设备,不建议传nil
* options:传入字典类型
默认值为false表示不会重复扫描已经发现的设备
key:CBCentralManagerScanOptionAllowDuplicatesKey
value:true
你想扫描的目标设备它所包含的serviceUUID数组
key:CBCentralManagerScanOptionSolicitedServiceUUIDsKey
value:[CBUUID]
*/
centralManager.scanForPeripherals(withServices: nil, options:nil)
可在CBCentralManagerDelegate的代理方法centralManagerDidUpdateState(_ central:)中调用此扫描方法,因为在初始化CBCentralManager后会自动调用此代理方法,而后需手动调用扫描方法开始扫描,做到效率最高,此代理方法在用户改变蓝牙状态后,会被反复调用,为了避免重复操作,需要对扫描方法做一些处理.
- 添加一个定时器,用于在过了超时时间后,自动停止扫描外设,节约用户的电量
- 添加一个标志位busy表示此时正在扫描中
开始扫描外设
func scan(duration:TimeInterval) throws {
guard !busy else { throw BikeScannerError.busy }
guard centralManager != nil else { throw BikeScannerError.noCentralManagerSet }
busy = true
centralManager.scanForPeripherals(withServices: configuration.servicesUUIDs, options:[CBCentralManagerScanOptionAllowDuplicatesKey: true])
timer = Timer.scheduledTimer(timeInterval: duration, target: self, selector: #selector(timerElapsed), userInfo: nil, repeats: false)
}
结束扫描的方法
func endScan() -> Void {
centralManager.stopScan()
busy = false
if let timer = timer {
timer.invalidate()
//成员变量scanHandlers是一个闭包scanHandlers?.completionHandler(discoveries)
}
discoveries.removeAll()
}
调用扫描方法后,就会自动调用下面这个代理方法
/*
* central:中心设备
* peripheral:发现的外设
* advertisementData:扫描到外设广播的数据
* rssi:外设的信号强度
*/
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
如果外设是手机设备,那么这个外设仅可以通过advertisementData发送包含两个字段的数据,通过上面的回调方法,接收广播数据:
[
CBAdvertisementDataLocalNameKey:"我是外设的名字",
CBAdvertisementDataServiceUUIDsKey:[serviceUUID]
]
所以第一步需要新建个模型(BTDiscovery)将扫描回调的信息存起来
struct BTDiscovery {
var advertisementData: [String : Any]
var RSSI: NSNumber
var peripheral:CBPeripheral
//外设的名字
var localName:String? {
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
}
//服务id
var uuid:Array<CBUUID>? {
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? Array<CBUUID>
}
//检索目标设备是否以约定字符串命名 比如: "Bluetooth1234"
func hasPrefix(_ peripheralName:String) -> Bool {
if let name = localName, name.hasPrefix(peripheralName) {
return true
}
else{
return false
}
}
}
extension BTDiscovery: Hashable, Equatable {
var hashValue: Int {
return peripheral.identifier.hashValue
}
}
func ==(lhs:BTDiscovery, rhs:BTDiscovery) -> Bool {
return lhs.peripheral == rhs.peripheral
}
接下来在回调方法中完成初始化,并存入成员变量discoveries数组中:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//用于避免重复扫描的标志位,后面会说到用处
guard busy else { return }
let remotePeripheral = BikePeripheral(peripheral)
let discovery = BikeDiscovery(advertisementData: advertisementData, remotePeripheral: remotePeripheral, RSSI: Int(RSSI))
if discovery.hasPrefix("BT") {
discovery.peripheral.start(withConfiguration: configuration)
//通过信号强度来过滤掉一些设备
if discovery.RSSI < 1 && discovery.RSSI > -75 {
//成员变量scanHandlers是一个闭包
scanHandlers?.progressHandler?(self, discovery)
}
discoveries.insert(discovery)
}
}
BikePeripheral是对CBPeripheral的再次封装,在发现服务和特征,写入或读取数据小节中会讲到,此处可忽略
其中变量scanHandlers声明如下:
private var scanHandlers:(progressHandler:ScanProgressHandler?, completionHandler:ScanCompletionHandler)?
下篇将介绍蓝牙连接类,戳这里
具体代码请参考GitHub链接