Leonardo Cardoso (@leocardz) começou a trabalhar com mobile desde 2010 com a plataforma do robozinho verde, mas veio para a maçã em 2014, atualmente lidera o time iOS da 1aim, em Berlim. É também um entusiasta open source e desenvolveu alguns projetos que podem ser acessados no seu perfil do GitHub.
Intro
É muito comum hoje estarmos em contato com dispositivos sem fio no nosso dia-a-dia. Seja um headphone, um mouse, um teclado, ou o famigerado Apple Watch. A lista é extensa… Todos eles, porém, utilizam basicamente uma mesma tecnologia que os torna de fato wireless: Bluetooth®.
Aqui vamos aprender brevemente os papeis executados e propriedades difundidas numa conexão via Bluetooth, e os modos de execução desses papeis utilizando o framework Core Bluetooth, que nos dá o suporte à versão 4.0 (a.k.a smart, a.k.a. low-energy).
Primeiramente vamos aos modos de execução.
Modos de Execução
O Core Bluetooth, ou CB, nos proporciona dois modos de execução, Foreground e Background. Assim, podemos definir bem se queremos nossos apps “conectáveis” apenas quando o usuário estiver interagindo com ele ou não. Alguns aspectos definem bem esses modos, são eles:
Foreground
Se o app é minimizado ou outro app se torna o principal utilizado pelo usuário, nosso suporte Bluetooth vai junto. Assim, no estado suspenso, seu app não é capaz de realizar tarefas relacionadas a Bluetooth, nem fica ciente de eventos relacionados a Bluetooth. Porém a lista de eventos recebidos enquanto o app estava em background é empilhada e esses eventos são entregues quando o usuário resumir o app, tornando-o ativo novamente.
Explicaremos mais abaixo a diferença entre os papeis e as propriedades, mas nesse modo caso o aplicativo não esteja ativo, o periférico não consegue enviar propagandas (advertising) e a central não consegue difundir seu sinal (scanning), porém a execução de advertising e scanning são respondidas sob demanda, praticamente sem atrasos.
A vantagem de usarmos em Foreground é que temos o todos os recursos do sistema ao nosso alcance, como por exemplo, atualização de views e tarefas sem tempo limite de execução. Ainda nesse modo, nossa stack de dispositivos é bem distrinchada, inclusive tendo a opção de escolher se queremos escanear o mesmo periférico por alguma razão específica. Isso é feito através de uma propriedade CBCentralManagerScanOptionAllowDuplicatesKey
, o que no modo Background é ignorada, assim como outras propriedades são tratadas diferentes quando executadas em modos Foreground e Background e elas podem ser achadas nas Referências.
Background
Aqui já começamos dizendo que os recursos de sistemas são limitados. Temos também 10 segundos no máximo para executar uma tarefa nesse modo. Mas creio que isso é mais do que suficiente. O intervalo de execução de advertising e scanning pode ser mais demorado. Isso é feito em prol de economizar bateria do seu dispositivo. Aqui, eventos do mesmo dispositivo são combinados num único evento com seus serviços e características, ou seja, a opção de escanear o mesmo dispositivo mais de uma vez é descartada.
A vantagem desse modo é permitir que nosso app não precisa estar ativo para trocarmos dados entre centrais e periféricos. Então, só para esclarecer, o sistema acorda nosso app de um estado suspenso, permitindo ler, escrever, inscrever em características, escutar eventos. Tudo em background. Mas mesmo que tenhamos permissão de rodar em background, nosso app não vai rodar sempre. Isso pode acontecer porque eventualmente o sistema pode finalizar nosso app para liberar memória para o app que está no estado ativo. A partir do iOS7, é permitido salvar os estados dos dispositivos, conexões pendentes, etc., antes de uma interrupção e então restaurá-los depois.
Como um exemplo para essa restauração de estados, vamos imaginar que você tenha desenvolvido um dispositivo de segurança que se comunica com uma fechadura eletrônica equipada com Bluetooth. O app e a fechadura podem interagir para que a fechadura se tranque automaticamente quando o usuário saia da casa e destranque quando o usuário voltar. Tudo isso com o iPhone no bolso. Quando o usuário sair de casa, o iPhone pode eventualmente sair do alcance da fechadura e, então, perder conexão. Nesse caso, o usuário pode simplesmente chamar a função para conectar o dispositivo, mesmo não estando no raio de alcance e, assim, a conexão será refeita quando eles estiverem de novo ao alcance. Isso acontece porque a requisição de conexão de dispositivos não expira.
Agora imagina que o usuário está fora de casa em uma viagem. Se o app é finalizado pelo sistema enquanto o usuário está fora, o app não vai ser capaz de se reconectar com a fechadura quando o usuário retornar. Assim, a fechadura não vai destrancar. Para apps como estes, é definitivamente necessário usar a opção de salvamento e restauração de estados.
Observação: macOS e iOS funcionam um pouco diferente nos papeis de periférico e central. Por exemplo, macOS não aceita a flag CBCentralManagerOptionRestoreIdentifierKey
, porque o background do macOS não finaliza apps como o iOS faz, em teoria.
Para usar o modo Background, precisamos ainda definir flags no Info.plist
.
bluetooth-peripheral
: Se quisermos usar um periférico em backgroundbluetooth-central
: Se quisermos usar uma central em background
Se adicionarmos o suporte via Target > Capabilities > Background Modes
, selecionando Uses Bluetooth LE accessories (central)
e/ou Act as Bluetooth LE accessory (peripheral)
, elas são adicionadas automaticamente no Info.plist
.
Importante: Não esqueça de adicionar a flag Privacy - Bluetooth Peripheral Usage Description
para que o usuário permita que esteja utilizando o dispositivo como periférico, assim como fazemos quando queremos utilizar a câmera ou localização.
Papeis
Nesse jogo de conexões Bluetooth, nós temos dois papeis distintos e bem especificados que funcionam como uma abordagem Client-Server. São eles: Periférico e Central.
Periférico
Um periférico é o dispositivo que compartilha seus dados para a central. Nesse modo, o sistema acorda nosso app para processar tarefas de leitura, escrita, inscrições de eventos vindos da central. É o dispositivo que funciona como Server, ou seja, tem os dados que se desejam.
Quando um periférico está sendo executado em Background, seus serviços são realocados para uma área de “overflow” especial, ou seja, todos os UUIDs dos serviços contidos na valor da propriedade CBAdvertisementDataServiceUUIDsKey
. Sendo assim, ele só pode ser descoberto por um dispositivo que esteja explicitamente escaneando por ele, procurando por alguma de sua característica, basicamente seu indentificador.
Central
Uma central é o dispositivo que requer informações de dispositivos periféricos. Nesse caso, ela escaneia, connecta, obtém dados e envia dados, os explora. O Client.
O sistema acorda nossa central quando eventos de mudança de estado ocorrem com o periférico, tais como conexão estabelecida ou cortada com o periférico, quando periférico atualiza informações de suas caractéristicas, ou ainda quando nossa central está perto de ser finalizada e também restaurada.
Exemplo
Imagina o funcionamento de um medidor de batimento cardíaco de praticantes de esportes. Nesse caso, o periférico seria o dispositivo medidor que está alocado abaixo do peito do corredor. E um app no iPhone poderia ler esse valor e mostrar para o usuário em tempo real, fazendo o iPhone funcionar como central, numa ação em Foreground. Agora imagina que o corredor não vai querer ficar olhando para seu iPhone todo o tempo, então, no modo Background, o iPhone captura dados dos batimentos e guarda num log no qual o usuário pode checar as variações quando terminar seu exercício.
Conexão
A conexão entre uma central e um periférico é feita através de escaneamento e propaganda. Basicamente esse é o fluxo:
- Um periférico difunde um sinal que pode ser conectado usando pacotes de propaganda;
- Enquanto a central difunde um sinal que está procurando por periférico;
- Quando uma central encontra um periférico, ele pode explorar primeiro requisitar a conexão, o que pode ser rejeitada pelo periférico ou não. Essa conexão pode ser encriptada com a encriptação nativa que o Core Bluetooth nos provê. Se a conexão encriptada for desejada, então um código aparece num dos dispositivos para ser digitado no outro e assim criar pares de criptografia administrados pelo próprio sistema, tornando-os dispositivos confiáveis. Se nenhuma criptografia for requerida, então a conexão é feita automática;
- Após a conexão ser feita, a central pode então ordenar que o periférico descubra serviços, basicamente a central está explorando os serviços do periférico;
- Após serviços descobertos, a central pode então ordenar que o periférico descubra características, basicamente a central está explorando as características de cada serviço do periférico;
- Descobertas as características, a central pode então ler valores das características estaticamente ou se increver naquela característica e caso o periférico atualize o valor dela, a central será notificada com o novo valor.
- A conexão pode ser finalizada, se for o caso.
Serviços e Características
As trocas de dados feitas por dispositivos conectados são através de propriedades e elas são serviços e características já comentadas em algumas partes anteriormente.
Um serviço é uma coleção de dados e comportamentos associados para completar uma tarefa ou uma função de um disposito, ou partes de um dispositivo. Esses comportamentos são chamados de características. Uma característica provê mais detalhes sobre um serviço de um periférico. Parece basicamente uma descrição daqueles verbetes que você procura em um dicionário. Uma definição te leva pra outra e você fica num eterno loop. Mas vamos tentar explicar melhor mais abaixo.
Serviços podem ter outros serviços relacionados, como dependências, apontando para UUIDs de outros serviços. Cada característica também tem UUID que a possa indentificar.
Então nesse exemplo nós temos dois sensores que funcionam diferentes um do outro mas juntos eles produzem um serviço, que é o Batimento Cardíaco. Para que ele funcione corretamente, o sensor cardíaco deve estar posicionado no local ideal.
Ainda utilizando o nosso exemplo anterior, suponha que no dispositivo de batimento cardíaco poderíamos ter dois serviços, um com duas caracterícticas e outro com apenas uma:
- Serviço 1: Batimentos Cardíacos
- Característica 1: Medição dos Batimentos Cardíacos
- Característica 2: Localização Adequada do Dispositivo
- Serviço 2: Status
- Característica 1: Nível de Bateria
Mas um periférico é nada se não estiver enviando propagandas. Um pacote de propaganda é relativamente um pequeno agrupamento de dados que tem informações sobre o que o dispositivo periférico para um reconhecimento inicial. Dados como nome do dispositivo, UUID e RSSI, que é uma informação de quão forte é o sinal do periférico.
Pra onde vamos nós?
Nós vamos exemplificar aqui o seguinte:
- Papeis
- iOS como Periférico
- macOS como Central
- Modos
- Foreground
Para conferir o modo background, confira no repositório desse projeto no GitHub, no branch background
.
Code Snippets
Devemos primeiro ligar nosso app com o CoreBluetooth
. Então, vá Project
-> Targets
-> Build Phases
-> Link Binary with Libraries
-> Procura por CoreBluetooth.framework
e então o adiciona.
Foreground
Primeiro, vamos criar um protocolor para receber os eventos escutados do BluetoothManager
e atualizar as views dos nossos ViewController
s. Vamos chamá-lo de BlueEar
. E tem uma versão para a Central
e outra pro Peripheral
. Assim como o BlueEar
, teremos uma classe que será a administradora da nossa conexão Bluetooth e é a BluetoothManager
.
iOS
BlueEar
protocol BlueEar {
func didStartConfiguration()
func didStartAdvertising()
func didSendData()
func didReceiveData()
}
BluetoothManager
class BluetoothManager: NSObject {
// MARK: - Properties
let peripheralId: String = "62443cc7-15bc-4136-bf5d-0ad80c459215"
let serviceUUID: String = "0cdbe648-eed0-11e6-bc64-92361f002671"
let characteristicUUID: String = "199ab74c-eed0-11E6-BC64-92361F002672"
let localName: String = "Peripheral - iOS"
let properties: CBCharacteristicProperties = [.read, .notify, .writeWithoutResponse, .write]
let permissions: CBAttributePermissions = [.readable, .writeable]
var bluetoothMessaging: BlueEar?
var peripheralManager: CBPeripheralManager?
var serviceCBUUID: CBUUID?
var characteristicCBUUID: CBUUID?
var service: CBMutableService?
var characterisctic: CBMutableCharacteristic?
// MARK: - Initializers
convenience init (delegate: BlueEar?) {
self.init()
self.bluetoothMessaging = delegate
guard
let serviceUUID: UUID = NSUUID(uuidString: self.serviceUUID) as UUID?,
let characteristicUUID: UUID = NSUUID(uuidString: self.characteristicUUID) as UUID?
else { return }
self.serviceCBUUID = CBUUID(nsuuid: serviceUUID)
self.characteristicCBUUID = CBUUID(nsuuid: characteristicUUID)
guard
let serviceCBUUID: CBUUID = self.serviceCBUUID,
let characteristicCBUUID: CBUUID = self.characteristicCBUUID
else { return }
// Configuring service
self.service = CBMutableService(type: serviceCBUUID, primary: true)
// Configuring characteristic
self.characterisctic = CBMutableCharacteristic(type: characteristicCBUUID, properties: self.properties, value: nil, permissions: self.permissions)
guard let characterisctic: CBCharacteristic = self.characterisctic else { return }
// Add characterisct to service
self.service?.characteristics = [characterisctic]
self.bluetoothMessaging?.didStartConfiguration()
// Initiate peripheral and start advertising
self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
}
}
CBPeripheralManagerDelegate
// MARK: - CBPeripheralManagerDelegate
extension BluetoothManager: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
print("peripheralManagerDidUpdateState")
if peripheral.state == .poweredOn {
guard let service: CBMutableService = self.service else { return }
self.peripheralManager?.removeAllServices()
self.peripheralManager?.add(service)
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
print("\ndidAdd service")
let advertisingData: [String: Any] = [
CBAdvertisementDataServiceUUIDsKey: [self.service?.uuid],
CBAdvertisementDataLocalNameKey: "Peripheral - iOS"
]
self.peripheralManager?.stopAdvertising()
self.peripheralManager?.startAdvertising(advertisingData)
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
print("peripheralManagerDidStartAdvertising")
self.bluetoothMessaging?.didStartAdvertising()
}
// Listen to dynamic values
// Called when CBPeripheral .setNotifyValue(true, for: characteristic) is called from the central
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print("\ndidSubscribeTo characteristic")
guard let characterisctic: CBMutableCharacteristic = self.characterisctic else { return }
do {
// Writing data to characteristics
let dict: [String: String] = ["Hello": "Darkness"]
let data: Data = try PropertyListSerialization.data(fromPropertyList: dict, format: .binary, options: 0)
self.peripheralManager?.updateValue(data, for: characterisctic, onSubscribedCentrals: [central])
self.bluetoothMessaging?.didSendData()
} catch let error {
print(error)
}
}
// Read static values
// Called when CBPeripheral .readValue(for: characteristic) is called from the central
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
print("\ndidReceiveRead request")
if let uuid: CBUUID = self.characterisctic?.uuid, request.characteristic.uuid == uuid {
print("Match characteristic for static reading")
}
}
// Called when receiving writing from Central.
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
print("\ndidReceiveWrite requests")
guard
let characteristicCBUUID: CBUUID = self.characteristicCBUUID,
let request: CBATTRequest = requests.filter({ $0.characteristic.uuid == characteristicCBUUID }).first,
let value: Data = request.value
else { return }
// Send response to central if this writing request asks for response [.withResponse]
print("Sending response: Success")
self.peripheralManager?.respond(to: request, withResult: .success)
print("Match characteristic for writing")
do {
if let receivedData: [String : String] = try PropertyListSerialization.propertyList(from: value, options: [], format: nil) as? [String: String] {
print("Written value is: \(receivedData)")
self.bluetoothMessaging?.didReceiveData()
} else {
return
}
} catch let error {
print(error)
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
print("\ndidUnsubscribeFrom characteristic")
}
func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String : Any]) {
print("willRestoreState")
}
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
print("peripheralManagerIsReady")
}
}
ViewController
import UIKit
class ViewController: UIViewController {
// MARK: - IBOutlet
@IBOutlet var label: UILabel!
// MARK: - Lifecyle
override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }
// MARK: - Properties
var manager: BluetoothManager?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.manager = BluetoothManager(delegate: self)
}
}
// MARK: - BlueEar
extension ViewController: BlueEar {
func didStartConfiguration() { self.label.text = "Start configuration 🎛" }
func didStartAdvertising() { self.label.text = "Start advertising 📻" }
func didSendData() { self.label.text = "Did send data ⬆️" }
func didReceiveData() { self.label.text = "Did received data ⬇️" }
}
View
Notas:
- No nosso exemplo, o Periférico vai começar a fazer propaganda quando iniciar o
app
. - Você só pode fazer alguma coisa com o periférico quando a função
peripheralManagerDidUpdateState(: CBPeripheralManager)
for chamada e o estado do periférico estiver.poweredOn
. - Leituras de valores dinâmicos pela Central usando a função
.setNotifyValue(true, for: characteristic)
disparam a funçãoperipheralManager(_: CBPeripheralManager, : CBCentral, : CBCharacteristic)
do periférico, já leituras de valores estáticos usando a função.readValue(for: characteristic)
pela Central, disparamperipheralManager(_: CBPeripheralManager, :CBATTRequest)
do periférico.
macOS
BlueEar
protocol BlueEar {
func didStartConfiguration()
func didStartScanningPeripherals()
func didConnectPeripheral(name: String?)
func didDisconnectPeripheral(name: String?)
func didSendData()
func didReceiveData()
func didFailConnection()
}
BluetoothManager
class BluetoothManager: NSObject {
// MARK: - Properties
let serviceUUID: String = "0cdbe648-eed0-11e6-bc64-92361f002671"
let characteristicUUID: String = "199ab74c-eed0-11e6-bc64-92361f002672"
var serviceCBUUID: CBUUID?
var characteristicCBUUID: CBUUID?
var blueEar: BlueEar?
var centralManager: CBCentralManager?
var discoveredPeripheral: CBPeripheral?
// MARK: - Initializers
convenience init (delegate: BlueEar) {
self.init()
self.blueEar = delegate
guard
let serviceUUID: UUID = NSUUID(uuidString: self.serviceUUID) as UUID?,
let characteristicUUID: UUID = NSUUID(uuidString: self.characteristicUUID) as UUID?
else { return }
self.serviceCBUUID = CBUUID(nsuuid: serviceUUID)
self.characteristicCBUUID = CBUUID(nsuuid: characteristicUUID)
}
// MARK: - Functions
func scan() {
self.centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
self.blueEar?.didStartConfiguration()
}
}
CBCentralManagerDelegate
// MARK: - CBCentralManagerDelegate
extension BluetoothManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("\ncentralManagerDidUpdateState \(Date())")
if central.state == .poweredOn {
guard let serviceCBUUID: CBUUID = self.serviceCBUUID else { return }
self.blueEar?.didStartScanningPeripherals()
self.centralManager?.scanForPeripherals(withServices: [serviceCBUUID], options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// We must keep a reference to the new discovered peripheral, which means we must retain it.
self.discoveredPeripheral = peripheral
print("\ndidDiscover:", self.discoveredPeripheral?.name ?? "")
self.discoveredPeripheral?.delegate = self
guard let discoveredPeripheral: CBPeripheral = self.discoveredPeripheral else { return }
self.centralManager?.connect(discoveredPeripheral, options: nil)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("\ndidConnect", self.discoveredPeripheral?.name ?? "")
self.blueEar?.didConnectPeripheral(name: peripheral.name ?? "")
guard let serviceCBUUID: CBUUID = self.serviceCBUUID else { return }
self.discoveredPeripheral?.discoverServices([serviceCBUUID])
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("willRestoreState")
}
func centralManager(_ central: CBCentralManager, didRetrievePeripherals peripherals: [CBPeripheral]) {
print("\ndidRetrievePeripherals")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("\ndidFailToConnect")
self.blueEar?.didFailConnection()
}
func centralManager(_ central: CBCentralManager, didRetrieveConnectedPeripherals peripherals: [CBPeripheral]) {
print("\ndidRetrieveConnectedPeripherals")
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("\ndidDisconnectPeripheral", self.discoveredPeripheral?.name ?? "")
self.blueEar?.didDisconnectPeripheral(name: peripheral.name ?? "")
}
}
CBPeripheralDelegate
// MARK: - CBPeripheralDelegate
extension BluetoothManager: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("\ndidDiscoverServices")
if let service: CBService = self.discoveredPeripheral?.services?.filter({ $0.uuid == self.serviceCBUUID }).first {
guard let characteristicCBUUID: CBUUID = self.characteristicCBUUID else { return }
self.discoveredPeripheral?.discoverCharacteristics([characteristicCBUUID], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("\ndidWriteValueFor \(Date())")
// After we write data on peripheral, we disconnect it.
self.centralManager?.cancelPeripheralConnection(peripheral)
// We stop scanning.
self.centralManager?.stopScan()
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("\ndidDiscoverCharacteristicsFor")
if let characteristic: CBCharacteristic = service.characteristics?.filter({ $0.uuid == self.characteristicCBUUID }).first {
print("Matching characteristic")
// To listen and read dynamic values
self.discoveredPeripheral?.setNotifyValue(true, for: characteristic)
// To read static values
// self.discoveredPeripheral?.readValue(for: characteristic)
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("\ndidUpdateValueFor")
// We read
if let value: Data = characteristic.value {
do {
let receivedData: [String: String] = try PropertyListSerialization.propertyList(from: value, options: [], format: nil) as! [String: String]
print("Value read is: \(receivedData)")
self.blueEar?.didReceiveData()
} catch let error {
print(error)
}
}
// We write
do {
print("\nWriting on peripheral.")
let dict: [String: String] = ["Yo": "Lo"]
let data: Data = try PropertyListSerialization.data(fromPropertyList: dict, format: .binary, options: 0)
self.discoveredPeripheral?.writeValue(data, for: characteristic, type: .withResponse)
self.blueEar?.didSendData()
} catch let error {
print(error)
}
}
func peripheralDidUpdateRSSI(_ peripheral: CBPeripheral, error: Error?) {
print("\nperipheralDidUpdateRSSI")
print(self.discoveredPeripheral?.rssi ?? "")
}
func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
print("\nperipheralDidUpdateName")
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
print("\ndidWriteValueFor")
}
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
print("\ndidModifyServices")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
print("\ndidUpdateValueFor")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) {
print("\ndidDiscoverIncludedServicesFor")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
print("\ndidDiscoverDescriptorsFor")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
print("\ndidUpdateNotificationStateFor")
}
}
ViewController
import Foundation
import Cocoa
class ViewController: NSViewController {
// MARK: - IBOutlet
@IBOutlet weak var label: NSTextField!
@IBOutlet weak var button: NSButton!
// MARK: - Properties
var manager: BluetoothManager?
// MARK: - Lifecyle
@IBAction func discover(_ sender: Any) {
self.manager = BluetoothManager(delegate: self)
self.manager?.scan()
}
}
// MARK: - BlueEar
extension ViewController: BlueEar {
func didStartConfiguration() { self.label.stringValue = "Start configuration 🎛" }
func didStartScanningPeripherals() { self.label.stringValue = "Start scanning peripherals 👀" }
func didConnectPeripheral(name: String?) { self.label.stringValue = "Did connect to: \(name ?? "") 🤜🏽🤛🏽" }
func didDisconnectPeripheral(name: String?) { self.label.stringValue = "Did disconnect: \(name ?? "") 🤜🏽🤚🏽" }
func didSendData() { self.label.stringValue = "Did send data ⬆️" }
func didReceiveData() { self.label.stringValue = "Did received data ⬇️" }
func didFailConnection() { self.label.stringValue = "Connection failed 🤷🏽♂️" }
}
View
Notas:
- No nosso exemplo, a Central vai começar a escanear por periféricos quando clicarmor num botão chamado
Tap
. - Você só pode fazer alguma coisa com a central quando a função
centralManagerDidUpdateState(: CBCentralManager)
for chamada e o estado do periférico estiver.poweredOn
. - Note que o
CBPeripheralDelegate
é diferente doCBPeripheralManagerDelegate
usado no periférico. Ambos são relacionados ao periférico, porém oCBPeripheralDelegate
permite que a central escute eventos sobre o periférico. - Ao optar por ler resultados dinâmicos, a central se inscreve em características do periférico, assim sendo alertada por meio de notificações se o periférico alterar seu valor no qual ela esteja inscrita.
- Quando a central descobre um periférico, devemos manter uma referência para ele, ou seja, devemos retê-lo fazendo com que uma variável dentro do nosso código o receba.
- Os dados são trasmitidos em formato de
Data
, então você deve transformar o que quer transmitir neste tipo de dados para poder ser recebido corretamente no outro lado.
Resultado
Na imagem, temos o respectivo: Log do periférico, iOS, macOS, Log da central.
Referências
Agradecimentos
Um agradecimento pra galera do Slack iOS Devs BR pela oportunidade.
Um abraço, e até próxima.
Leo