Usar o RxBluetoothKit é uma boa maneira de acessar o dispositivo Bluetooth, especialmente acessando vários serviços / características.
Objetivo
(1) leia a Característica A1 do Serviço A
(2) leia a Característica B1 do Serviço B
(3) assine a Característica B2 do Serviço B e receba a notificação nas mesmas vezes que o valor obtido em (2)
Meio Ambiente
Xcode: 10.1
Swift: 4.2.1
RxBluetoothKit: 5.1.4
RxBluetoothKit
https://github.com/Polidea/RxBluetoothKit
Código fonte
1. Defina os serviços
enum BLEService: String, ServiceIdentifier {
case serviceA = "UUID of ServiceA"
case serviceB = "UUID of ServiceB"
var uuid: CBUUID {
return CBUUID(string: self.rawValue)
}
}
2. Defina as características
enum BLECharacteristic: String, CharacteristicIdentifier {
case charA1 = "UUID of CharacteristicA1"
case charB1 = "UUID of CharacteristicB1"
case charB2 = "UUID of CharacteristicB2"
var uuid: CBUUID {
return CBUUID(string: self.rawValue)
}
var service: ServiceIdentifier {
switch self {
case .charA1:
return BLEService.serviceA
case .charB1, .charB2:
return BLEService.serviceB
}
}
}
3. Digitalize e conecte ao dispositivo BLE
Defino tempos limite de 3 segundos para resolver o erro, quando o bluetooth está desligado ou a central não consegue se conectar ao periférico.
Adotei o take (1), a fim de limitar o evento ligado e o dispositivo a se conectar a 1 cada.
* No iOS 11 e 12, o Control Center não pode desligar o bluetooth, mas muda para o estado desconectado.
Nesse estado, nenhum alerta é mostrado, mesmo se CBCentralManagerOptionShowPowerAlertKey for verdadeiro, mas podemos capturar como um evento de tempo limite de inicialização.
func connect() -> Observable<Peripheral> {
let options = [CBCentralManagerOptionShowPowerAlertKey: true] as [String : AnyObject]
let manager = CentralManager(queue: .main, options: options)
return manager.observeState()
.startWith(self.manager.state)
.filter { $0 == .poweredOn }
.timeout(3.0, scheduler: MainScheduler.instance)
.take(1)
.flatMap { _ in self.manager.scanForPeripherals(withServices: [BLEService.serviceA.uuid, BLEService.serviceB.uuid]) }
.timeout(3.0, scheduler: MainScheduler.instance)
.take(1)
.flatMap { $0.peripheral.establishConnection() }
}
4. Leia vários serviços / características
O que fazer aqui é:
(1) ler CharacteristicA1 de ServiceA
(2) ler CharacteristicB1 de ServiceB
Usando Observable.concat, posso ler vários serviços / características.
* Valores de análise omitidos aqui
func readData() {
connect()
.flatMap {
Observable.concat(
$0.readValue(for: BLECharacteristic.charA1).asObservable(),
$0.readValue(for: BLECharacteristic.charB1).asObservable()
)}
.subscribe(onNext: { (char) in
switch char.uuid {
case BLECharacteristic.charA1.uuid:
let data = char.value
case BLECharacteristic.charB1.uuid:
let data = char.value
// call get notify method
getNotify(char: char, notifyCount: Int(data))
}, onError: { (error) in
// error handling
}, onCompleted: {
}, onDisposed: {
})
}
5. Seja notificado de acordo com o resultado da leitura
O que fazer aqui é:
(3) inscrever a CharacteristicB2 do ServiceB e receber a notificação nas mesmas vezes que o valor obtido em (2)
Eu chamo esse método em onNext em “4. Ler de vários serviços / características”.
func getNotify(char : Characteristic, notifyCount : Int) {
char.service.discoverCharacteristics([BLECharacteristic.charB2.uuid]).asObservable()
.flatMap { Observable.from($0) }
.flatMap { $0.observeValueUpdateAndSetNotification() }
.take(notifyCount)
.subscribe(onNext: { (char) in
let data = char.value
}, onError: { (error) in
// error handling
}, onCompleted: {
}, onDisposed: {
})
}
6. Descarte o observável para evitar vazamento de memória
Se eu pegar (n), observable será descartado automaticamente após obter o valor n vezes.
Implementei take (n) em “5. Receber notificação de acordo com o resultado da leitura”, mas não em “4. Ler de vários serviços / características”.
Isso porque se eu implementar em “4. Leitura de vários serviços / características”, a conexão bluetooth será descartada antes de “5. Receber notificação de acordo com o resultado da leitura” e irei obter um erro.
Mas, dessa forma, o observável não será descartado e causará um vazamento de memória.
Minha solução para este problema é manter observável como uma variável membro e descartá-lo no próximo acesso.
func readData() {
if let d = disposable {
d.dispose()
}
disposable =
connect()
.flatMap ... // see "4. Read from Multiple Services/Characteristics"
}