Recentemente, nossa equipe criou e implementou a função de transferir dinheiro por via aérea usando a tecnologia Bluetooth LE. Quero contar como fizemos e o que a Apple nos fornece a partir das ferramentas. Muitos desenvolvedores acham que o Bluetooth é difícil, porque é um protocolo de baixo nível e não há muitos especialistas nele. Mas nem tudo é tão assustador e, de fato, o uso dessa função é muito simples! E aquelas funções que podem ser implementadas usando o Bluetooth LE são certamente interessantes e posteriormente destacarão sua aplicação entre os concorrentes.

Vamos primeiro entender que tipo de tecnologia é e qual a diferença do Bluetooth clássico.
O que é o Bluetooth LE?
Por que os desenvolvedores do Bluetooth chamaram essa tecnologia de baixa energia? Afinal, a cada nova versão do Bluetooth, o consumo de energia já era muitas vezes menor. A resposta está nesta bateria.
Seu diâmetro é de apenas 2 cm e a capacidade é de cerca de 220 mA * h. Quando os engenheiros desenvolveram o Bluetooth LE, eles queriam que o dispositivo com essa bateria funcionasse por vários anos. E eles fizeram isso! Os dispositivos Bluetooth LE com essa bateria podem funcionar por um ano. Quantos de vocês ainda desligam o Bluetooth do telefone da maneira antiga para economizar energia, como fizeram em 2000? Em vão você faz isso - a economia será inferior a 10 segundos do telefone por dia. E você desabilita funcionalidades muito grandes, como Handoff, AirDrop e outras.
O que os engenheiros conseguiram ao desenvolver o Bluetooth LE? Eles refinaram o protocolo clássico? Tornou a energia mais eficiente? Apenas otimizou todos os processos? Não. Eles redesenharam completamente a arquitetura da pilha Bluetooth e alcançaram o fato de que agora, para estar visível a todos os outros dispositivos, você precisa de menos tempo para estar no ar e ocupar o canal. Por sua vez, isso permitiu uma boa economia no consumo de energia. E com a nova arquitetura, qualquer novo dispositivo pode agora ser padronizado, graças ao qual desenvolvedores de todo o mundo podem se comunicar com o dispositivo e, portanto, escrever facilmente novos aplicativos para gerenciá-lo. Além disso, o princípio da autodescoberta está embutido na arquitetura: ao conectar-se a um dispositivo, você não precisa inserir nenhum código PIN e, se o seu aplicativo puder se comunicar com esse dispositivo, a conexão levará alguns milissegundos.
- Menos tempo no ar.
- Menos consumo de energia.
- Nova arquitetura.
- Tempo de conexão reduzido.
Como os engenheiros conseguiram dar um salto tão grande na eficiência energética?A frequência permaneceu a mesma: 2,4 GHz, não certificado e gratuito para uso em muitos países. Mas o atraso da conexão se tornou menor: 15-30 ms em vez de 100 ms com o Bluetooth clássico. A distância de trabalho permaneceu a mesma - 100 m. O intervalo de transmissão não foi forte, mas mudou - em vez de 0,625 ms, passou a 3 ms.
Mas por causa disso, o consumo de energia não pôde ser reduzido em dez vezes. Claro, algo tinha que sofrer. E esta é a velocidade: em vez de 24 Mbps, tornou-se 0,27 Mbps. Você provavelmente dirá que essa é uma velocidade ridícula para 2018.
Onde o Bluetooth LE é usado?

Esta tecnologia não é jovem, apareceu pela primeira vez no iPhone 4s. E já conseguiu conquistar muitas áreas. O Bluetooth LE é usado em todos os dispositivos domésticos inteligentes e eletrônicos vestíveis. Agora existem até chips do tamanho de grãos de café.
E como essa tecnologia é aplicada em software?Desde que a Apple foi a primeira a integrar o Bluetooth ao dispositivo e começar a usá-lo, agora eles fizeram um bom progresso e integraram a tecnologia ao seu ecossistema. E agora você pode conhecer essa tecnologia em serviços como AirDrop, Início rápido de dispositivos, Compartilhar senhas, Entrega. E até as notificações no relógio são feitas via Bluetooth LE. Além disso, a Apple disponibilizou publicamente a documentação sobre como garantir que as notificações de todos os aplicativos cheguem aos seus próprios dispositivos. Quais são as funções dos dispositivos no Bluetooth LE?
Brodcaster. Envia mensagens para todos que estão por perto, você não pode se conectar a este dispositivo. Por esse princípio, iBeacons e navegação interna funcionam.
Observador. Escuta o que está acontecendo e recebe dados apenas de mensagens públicas. Não cria conexões.
Mas com
Central e
Periférica mais interessante. Por que eles não foram chamados simplesmente de Cliente-Servidor? Logicamente, a julgar pelo nome. Mas não.
Porque o Peripheral realmente atua como um servidor. Este é um dispositivo periférico que consome menos energia e se conecta à Central mais poderosa. A Peripheral pode informá-lo de que está próximo e de quais serviços possui. Somente um dispositivo pode se conectar a ele e o Peripheral possui alguns dados. E o Central pode escanear o ar em busca de dispositivos, enviar solicitações de conexão, conectar-se a qualquer número de dispositivos, pode ler, escrever e assinar dados do Peripheral.
O que nós, como desenvolvedores, temos acesso no ecossistema da Apple?
O que está disponível para nós?
iOS / Mac OS:- Periférico e Central.
- Modo de fundo.
- Recuperação de estado.
- Intervalo de conexão 15 ms.
watchOS / tvOS:- watchOS 4+ / tvOS 9+.
- Apenas central.
- Máximo de duas conexões.
- Apple watch series 2+ / AppleTv 4+.
- Desligar ao entrar em segundo plano.
- Intervalo de conexão 30 ms.
A diferença mais importante é o intervalo de conexão. O que isso afeta? Para responder a essa pergunta, você primeiro precisa entender como o protocolo Bluetooth LE funciona e por que uma diferença tão pequena nos valores absolutos é muito importante.
Como o protocolo funciona
Como é o processo de busca e conexão?
O Peripheral anuncia sua presença na frequência do intervalo do anúncio, seu pacote é muito pequeno e contém apenas alguns identificadores de serviço fornecidos pelo dispositivo, bem como o nome do dispositivo. O intervalo pode ser bastante grande e pode variar dependendo do status atual do dispositivo, do modo de economia de energia e de outras configurações. A Apple aconselha os desenvolvedores de dispositivos externos a vincular a duração do intervalo ao acelerômetro: aumente o intervalo se o dispositivo não for usado e, quando estiver ativo, diminua para encontrar o dispositivo rapidamente. O intervalo de anúncio não se correlaciona com o intervalo de conexão e é determinado pelo próprio dispositivo, dependendo do consumo de energia e de suas configurações. É inacessível e desconhecido para nós no ecossistema da Apple; é completamente controlado pelo sistema.

Depois de encontrarmos o dispositivo, enviamos uma solicitação de conexão, e aqui o intervalo de conexão entra em cena - o tempo após o qual o segundo dispositivo pode responder à solicitação. Mas isso é quando conectar, mas o que acontece ao ler / escrever?

O intervalo de conexão também aparece ao ler os dados - reduzindo-o em 2 vezes aumenta a taxa de transferência de dados. Mas você precisa entender que, se os dois dispositivos não suportam o mesmo intervalo, o máximo deles será selecionado.
Vejamos em que consiste um pacote de informações que o Peripheral passa.
A MTU (unidade máxima de transmissão) desse pacote é determinada durante o processo de conexão e varia de dispositivo para dispositivo e dependendo do sistema operacional. No protocolo versão 4.0, o MTU era de cerca de 30 e o tamanho da carga útil não excedia 20 bytes. Na versão 4.2, tudo mudou, agora você pode transferir cerca de 520 bytes. Infelizmente, apenas dispositivos menores que o iPhone 5s suportam esta versão do protocolo. O tamanho da sobrecarga, independentemente do tamanho da MTU, é de 7 bytes: inclui os cabeçalhos ATT e L2CAP. Com o registro, em geral, uma situação semelhante.

Existem apenas dois modos: com resposta e sem. O modo não respondido acelera significativamente a transferência de dados, pois não há intervalo de espera antes da próxima gravação. Mas esse modo nem sempre está disponível, nem em todos os dispositivos e nem em todos os sistemas. O acesso a este modo de gravação pode ser limitado pelo próprio sistema, porque é considerado menos eficiente em termos de energia. No iOS, existe um método no qual você pode verificar antes de gravar se esse modo está disponível.
Agora, vejamos em que consiste o protocolo.

O protocolo consiste em 5 níveis.
A camada de aplicação é sua lógica, descrita na parte superior do CoreBluetooth.
O GATT (camada de atributos genéricos) é usado para trocar serviços e características que estão nos dispositivos.
ATT (Attributes Layer) é usado para gerenciar suas características e transferir seus dados.
O L2CAP é um protocolo de troca de dados de baixo nível.
O controlador é o próprio chip BT.
Você provavelmente pergunta o que é o GATT e como podemos trabalhar com ele?O GATT consiste em recursos e serviços. Uma característica é um objeto no qual seus dados são armazenados, como uma variável. E um serviço é um grupo no qual suas características estão localizadas, como um espaço para nome. O serviço tem um nome - UUID, você mesmo escolhe. Um serviço pode conter um serviço subsidiário.

A característica também possui seu próprio
UUID - na verdade, um nome.
O valor da característica é NSData, aqui você pode registrar e armazenar dados.
Descritores são uma descrição de sua característica, você pode descrever quais dados você espera nessa característica ou o que eles significam. Existem muitos descritores no protocolo Bluetooth, mas até agora apenas dois estão disponíveis nos sistemas Apple: a descrição humana e o formato dos dados. Também existem permissões para o seu recurso:

Vamos tentar você mesmo
Tivemos uma idéia de possibilitar a transferência de dinheiro por via aérea sem exigir nada do destinatário. Imagine que você está confundindo uma tarefa muito interessante, escrevendo o código perfeito, e aqui um colega sugere que você tome um café. E você é tão apaixonado pela tarefa que não pode ir embora, e peça a ele que lhe compre uma xícara de cappuccino delicioso. Ele traz café e você precisa devolver o dinheiro para ele. Você pode traduzir pelo número de telefone, funciona bem. Mas aqui está uma situação embaraçosa - você não sabe o número dele. Bem, assim, você trabalha há três anos, mas eles não trocaram números :)
Portanto, decidimos possibilitar a transferência de dinheiro para aqueles que estão por perto, sem inserir nenhum dado do usuário. Como no AirDrop. Basta selecionar um usuário e enviar a quantidade que ele precisa. Vamos ver o que precisamos para isso.

Mapeamento PUSH
Precisamos do remetente:
- Eu poderia encontrar todos os dispositivos que estão por perto e oferecer suporte ao nosso serviço.
- Eu poderia ler os detalhes.
- E ele poderia enviar uma mensagem ao destinatário de que ele havia enviado o dinheiro com sucesso.
O destinatário, por sua vez, deve ser capaz de informar os remetentes ao redor que ele possui um serviço com os dados necessários e receber mensagens do remetente. Acho que não vale a pena descrever como ocorre o processo de transferência de dinheiro por detalhes em nosso banco. Agora vamos tentar implementar isso.
Primeiro, você precisa criar os nomes de nossos serviços e características. Como eu disse, este é o UUID. Simplesmente os geramos e os salvamos no Peripheral e no Central para que sejam os mesmos nos dois dispositivos.

Você pode usar quaisquer UUIDs, exceto os que terminam assim: XXXXXXXX-
0000-1000-8000-00805F9B34FB - eles são reservados para empresas diferentes. Você mesmo pode comprar esse número e ninguém o usará. Vai
custar US $ 2500.
Em seguida, precisaremos criar gerentes: um para transferir fundos e outro para receber. Você só precisa especificar delegados. Vamos transmitir Central, receber Periférico. Criamos os dois, porque o remetente e o destinatário podem ser uma pessoa em momentos diferentes.

Agora precisamos tornar possível detectar o destinatário e anotar os detalhes do destinatário em nossa característica.

Primeiro, crie um serviço. Vamos registrar o UUID e indicar que ele é primário - ou seja, o serviço é o principal para este dispositivo. Um bom exemplo: um monitor de freqüência cardíaca, para o qual a freqüência cardíaca atual será o principal serviço e o status da bateria é uma informação secundária.
Em seguida, criamos duas características: uma para ler os detalhes do destinatário e a segunda para escrever para que ele possa aprender sobre o envio de dinheiro. Nós os registramos em nosso serviço, depois os adicionamos ao gerente, executamos a descoberta e indicamos o UUID do serviço para que todos os dispositivos próximos possam descobrir nosso serviço antes de conectá-lo. Esses dados são colocados no pacote que o Central envia durante a transmissão.
O destinatário está pronto, prossiga para o remetente. Execute a pesquisa e conecte-se.

Quando você liga o gerente, iniciamos a pesquisa de dispositivos com nosso serviço. Quando os encontramos, os colocamos no método delegate e nos conectamos imediatamente. Importante: você precisa manter um forte vínculo com todos os periféricos com os quais trabalha, caso contrário, eles vazarão.

Após a conexão bem-sucedida, configuramos o delegado que trabalhará com este dispositivo e obtemos o serviço necessário.

Conectamos com sucesso o destinatário, agora você precisa ler seus detalhes.
Após a conexão, já solicitamos todos os serviços do dispositivo. E depois de recebê-los, o método delegado será chamado, que listará todos os serviços disponíveis neste dispositivo. Nós encontramos o caminho certo e solicitamos suas características. O resultado pode ser encontrado pelo UUID no método delegate, que armazena os dados para tradução. Tentamos lê-los e obtemos o desejado novamente no método delegado. Todos os serviços, características e seus valores são armazenados em cache pelo sistema, portanto, não é necessário solicitá-los posteriormente cada vez.

É isso, enviamos dinheiro para o café, é hora de mostrar ao destinatário um belo aviso para que ele espere rublos em sua conta. Para fazer isso, você precisa implementar o processo de envio de uma mensagem.
Obtemos a característica de que precisamos do remetente, nesse caso, a retiramos do valor armazenado. Mas antes disso, você precisa obtê-lo do dispositivo, como fizemos antes. E então apenas escreva os dados na característica desejada.
Depois disso, no outro dispositivo, recebemos uma solicitação de gravação no método delegado. Aqui você pode ler os dados que lhe são enviados, responder a qualquer erro, por exemplo, não há acesso ou essa característica não existe. Tudo funcionará, mas apenas se os dois dispositivos estiverem ligados e os aplicativos estiverem ativos. E precisamos trabalhar em segundo plano!

A Apple permite que você use o Bluetooth em segundo plano. Para fazer isso, você precisa especificar em info.plist a chave em que modo queremos usar, em Periférico ou Central.

Em seguida, no gerente, você precisa especificar a chave de recuperação e criar um método de delegação. Agora, o modo de segundo plano está disponível para nós. Se o aplicativo adormecer ou for descarregado da memória, quando você encontrar o Periférico desejado ou quando o Central estiver conectado, ele será ativado e o gerente será restaurado com sua chave.

Está tudo bem, pronto para ser lançado. Mas aqui os designers vêm correndo até nós e dizem: "Queremos inserir fotos dos usuários para que sejam mais fáceis de se encontrar". O que fazer? Em nossa característica, você pode escrever apenas cerca de 500 bytes, mas em alguns dispositivos em geral 20 :(

Vá mais fundo
Para resolver esse problema, tivemos que ir mais fundo.

Agora conversamos com dispositivos no nível GATT / ATT. Mas no iOS 11, temos acesso ao protocolo L2CAP. No entanto, nesse caso, você precisará cuidar da transferência de dados. Os pacotes são enviados com MTU de 2 Kb, não é necessário recodificar nada, é aplicado o NSStream regular. Taxas de dados de até 394 Kbps, de acordo com a Apple.
Suponha que você transfira quaisquer dados do seu serviço do Periférico para o Central na forma de características normais. E demorei para abrir o canal. Você o abre no Peripheral. Em troca, você obtém o PSM - este é o número do canal ao qual você pode se conectar e é necessário transferi-lo para o Central usando as mesmas características. O número é dinâmico, o próprio sistema escolhe qual PSM abrir no momento. Após a transferência, você já pode se conectar ao Peripheral na Central e trocar dados em um formato conveniente para você. Vamos ver como fazer isso.
Primeiro, abra a porta criptografada no Peripheral. Você pode fazer isso sem criptografia, o que acelerará um pouco a transferência.

Em seguida, no método delegado, obtemos o PSM e o enviamos para outro dispositivo.

Após conectar outro dispositivo, seremos chamados de um método no qual podemos obter o NSStream necessário para a transmissão a partir do canal.

Central é ainda mais fácil, basta conectar-se ao canal com o número desejado ...

... e depois disso obtemos os fluxos de que precisamos. Neles, você pode transferir absolutamente qualquer dado de qualquer tamanho e criar seu protocolo sobre o L2CAP. Então, percebemos a transferência de fotos dos destinatários.

Mas existem armadilhas, onde ficar sem elas.
Armadilhas
Vejamos as armadilhas ao trabalhar em segundo plano. Como os papéis de Periférico e Central estão disponíveis para você, você pode pensar. que, em segundo plano, você pode determinar quais dispositivos estão próximos em segundo plano e quais estão ativos. Em teoria, deveria ter sido, mas a Apple introduziu uma restrição: os telefones em segundo plano, sejam centrais ou periféricos, não estão disponíveis para outros telefones que também estão em segundo plano. Além disso, os telefones em segundo plano não são visíveis em dispositivos não iOS. Vamos ver por que isso acontece.
Quando o dispositivo está ativo, ele envia um pacote de transmissão regular, que pode conter o nome do dispositivo e uma lista de serviços. que este dispositivo fornece. E o excesso de dados é tudo o que não se encaixa.

Quando o dispositivo entra em segundo plano, ele não transmite o nome e transfere a lista de serviços suportados para estourar dados. Se o aplicativo estiver ativo, ao digitalizar a partir de um dispositivo iOS, ele lê esses dados e, ao mudar para o segundo plano, ele os ignora. Portanto, ao alternar para o segundo plano, você não poderá ver aplicativos que também estão em segundo plano.
Outros sistemas operacionais da Apple sempre ignoram os dados excedentes; portanto, se você procurar dispositivos compatíveis com o seu serviço, obterá uma matriz vazia. E se você se conectar a cada dispositivo próximo e solicitar serviços suportados, a lista poderá conter seu serviço e você poderá trabalhar com ele.
Além disso, já estávamos nos preparando para enviar para teste, corrigindo pequenas falhas e estávamos envolvidos na otimização. E de repente, em algum momento, começamos a receber esse erro no console:CoreBluetooth[WARNING] Unknown error: 124
O pior foi que nenhum método delegado foi chamado; não foi possível vencer esse erro para o usuário. Apenas uma mensagem no log - e silêncio, tudo congelou. Nenhuma mudança importante foi feita, então começamos a reverter as confirmações. E eles descobriram que uma vez otimizaram o código e refizeram a maneira de escrever dados. O problema era que nem todos os clientes foram atualizados, portanto esse erro ocorreu.
.write != .writeWithoutResponse
Feliz por termos consertado tudo, corremos para passá-lo aos testes e eles quase imediatamente voltaram para nós: “Suas fotos de moda não funcionam. Todos eles estão sobrecarregados. Começamos a tentar, e é verdade que, às vezes, em dispositivos diferentes, fotos quebradas aparecem em momentos diferentes. Eles começaram a procurar por um motivo.
E, novamente, eles viram o erro anterior. Imediatamente pensei que estava em versões diferentes. Mas após a remoção completa da versão antiga de todos os dispositivos de teste, o erro ainda é reproduzido. Ficamos tristes ...
CoreBluetooth[WARNING] Unknown error: 722 CoreBluetooth[WARNING] Unknown error: 249 CoreBluetooth[WARNING] Unknown error: 312
Começamos a procurar uma ferramenta de depuração. A primeira coisa que encontramos foi o Apple Bluetooth Explorer. Um programa poderoso, ele pode fazer muitas coisas, mas para depurar o protocolo Bluetooth LE, há uma pequena guia com a pesquisa de dispositivos e a obtenção de características. E precisávamos analisar o L2CAP.
Então eles encontraram o LightBlue Explorer. Acabou sendo um programa bastante decente, embora com um design do iOS 7. Ele pode fazer a mesma coisa que o Bluetooth Explorer e também sabe como se inscrever nas especificações. E funciona mais estável. Está tudo bem, mas novamente sem o L2CAP.
E então nos lembramos do conhecido farejador WireShark.
Acabou que ele estava familiarizado com o Bluetooth LE: ele pode ler L2CAP, mas apenas no Windows. Embora não seja assustador não encontrarmos o Windows ou algo assim. O maior sinal de menos - o programa só funciona com um dispositivo específico. Ou seja, você precisava encontrar o dispositivo em algum lugar da loja oficial. E você mesmo entende que é improvável que uma grande empresa aprove a compra de um dispositivo incompreensível em um mercado de pulgas. Nós até começamos a navegar em lojas on-line no exterior.
Mas aqui eles encontraram o programa PacketLogger em Ferramentas adicionais do Xcode. Ele permite que você assista o tráfego que entra no dispositivo OS X. Por que não reescrever nosso MoneyDrop no OS X? Já tínhamos uma biblioteca separada. Acabamos de substituir o UIImage pelo NSImage, tudo começou após 10 minutos.

Finalmente, pudemos ler os pacotes trocados entre os dispositivos. Imediatamente ficou claro que, no momento da transmissão de dados via L2CAP, uma das características estava registrada. E devido ao fato de o canal estar completamente ocupado com a transferência de fotos, o iOS ignorou a gravação e o remetente após a ignição interrompeu o canal. Depois de corrigir os problemas com a transferência da foto não era.

Isso é tudo, obrigado pela leitura :)
Links úteis
WWDC / CoreBluetooth:BluetoothYouTube- Seta Eletrônica → Bluetooth Low Energy Series