Android Bluetooth de baixa energia (BLE) - Cooking Right, Part # 1

No ano passado, desenvolvi aplicativos Bluetooth Low Energy ( BLE ) para iOS e acabou sendo bastante simples. Então houve portá-los para Android ... quão difícil pode ser isso?





Posso dizer com certeza - foi mais difícil do que eu imaginava, tive que colocar muito esforço em um trabalho estável no Android. Estudei muitos artigos de domínio público, alguns se revelaram errôneos, muitos foram muito úteis e ajudaram no assunto. Nesta série de artigos, quero descrever minhas descobertas para que você não perca muito tempo pesquisando como eu.





Recursos do BLE para Android

  • A documentação do Google sobre o BLE é muito geral , em alguns casos, informações importantes estão faltando ou desatualizadas, os aplicativos de amostra não mostram como usar o BLE corretamente. Encontrei apenas algumas fontes sobre como fazer BLE corretamente. A apresentação de Stuart Kent  oferece um excelente material de partida. Para alguns tópicos avançados, há um bom artigo  nórdico .





  • A API Android BLE é uma operação de baixo nível , em aplicativos reais você precisa usar várias camadas de abstração (como, por exemplo, feito fora da caixa no iOS-CoreBluetooth). Normalmente você precisa fazer isso sozinho: fila de comandos, vinculação, manutenção da conexão, tratamento de erros e bugs, acesso multi-threaded. As bibliotecas mais famosas são  SweetBlueRxAndroidBle  e  Nordic . Na minha opinião, o mais fácil de aprender é o nórdico,  veja detalhes aqui .





  • Os fabricantes fazem alterações na pilha Android BLE  ou substituem-na completamente por sua própria implementação. E devemos levar em conta a diferença de comportamento para diferentes dispositivos no aplicativo. O que funciona bem em um telefone pode não funcionar em outros! Em geral, nem tudo é tão ruim, por exemplo, a implementação da Samsung é melhor do que a própria implementação do Google!





  • O Android tem vários bugs conhecidos (e desconhecidos)  que precisam ser corrigidos, especialmente nas versões 4.5 e 6. As versões posteriores funcionam muito melhor, mas também têm certos problemas, como falhas de conexão aleatórias com o erro 133 . Mais sobre isso abaixo.





Não finjo que resolvi todos os problemas, mas consegui atingir um nível "aceitável". Vamos começar com a digitalização.





Dispositivos de digitalização

.  BluetoothLeScanner



:





BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();

if (scanner != null) {
    scanner.startScan(filters, scanSettings, scanCallback);
    Log.d(TAG, "scan started");
}  else {
    Log.e(TAG, "could not get scanner object");
}
      
      



  filters



  scanSettings



,  scanCallback



:





private final ScanCallback scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        BluetoothDevice device = result.getDevice();
        // ...do whatever you want with this found device
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        // Ignore for now
    }

    @Override
    public void onScanFailed(int errorCode) {
        // Ignore for now
    }
};
      
      



 ScanResult



 ,  BluetoothDevice



, . , , ScanResult



  :





  • Advertisement data - , UUID ,  filters



      UUID .





  • RSSI  - ( ).





  • … , .  ScanResult



     .





 Activity



onScanResult



  ,  Activity



  ,  onScanResult



.





null , , UUID .





UUID

, UUID: 1810.  Advertisement data UUID , . , ,  Advertisement data , .





. : , UUID  Advertisement data, .





:





UUID BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb");
UUID[] serviceUUIDs = new UUID[]{BLP_SERVICE_UUID};
List<ScanFilter> filters = null;
if(serviceUUIDs != null) {
    filters = new ArrayList<>();
    for (UUID serviceUUID : serviceUUIDs) {
        ScanFilter filter = new ScanFilter.Builder()
                .setServiceUuid(new ParcelUuid(serviceUUID))
                .build();
        filters.add(filter);
    }
}
scanner.startScan(filters, scanSettings, scanCallback);
      
      



UUID ( 1810



),  16-bit UUID



   128-bit UUID



 (  00001810-000000-1000-8000-000-00805f9b34fb



). UUID BASE_PART UUID, .  .





, :









  • , , Polar H7 «Polar H7 391BBB014», - «Polar H7» , «391BBB014» - . . «Polar H7», ,  ScanResult



    .    :





String[] names = new String[]{"Polar H7 391BB014"};
List<ScanFilter> filters = null;
if(names != null) {
    filters = new ArrayList<>();
    for (String name : names) {
        ScanFilter filter = new ScanFilter.Builder()
                .setDeviceName(name)
                .build();
        filters.add(filter);
    }
}
scanner.startScan(filters, scanSettings, scanCallback);
      
      



MAC-.

   . MAC- , , , . , , Bluetooth.





String[] peripheralAddresses = new String[]{"01:0A:5C:7D:D0:1A"};
// Build filters list
List<ScanFilter> filters = null;
if (peripheralAddresses != null) {
    filters = new ArrayList<>();
    for (String address : peripheralAddresses) {
        ScanFilter filter = new ScanFilter.Builder()
                .setDeviceAddress(address)
                .build();
        filters.add(filter);
    }
}
scanner.startScan(filters, scanSettings, scanByServiceUUIDCallback);
      
      



, UUID, MAC- . , . .





ScanSettings

ScanSettings



Android . , , :





ScanSettings scanSettings = new ScanSettings.Builder()
        .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
        .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
        .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
        .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
        .setReportDelay(0L)
        .build();
      
      



ScanMode

, . Bluetooth . , . 4 ,  Nordics :





  1. SCAN_MODE_LOW_POWER



    . Android 0.5, 4.5. , advertisement .





  2. SCAN_MODE_BALANCED



    . : 2, : 3, «» .





  3. SCAN_MODE_LOW_LATENCY



    . , Android , , . . .





  4. SCAN_MODE_OPPORTUNISTIC



    . , ! , , . Android , (. « »).





Callback Type

callback  ScanResult



  , 3 :





  1. CALLBACK_TYPE_ALL_MATCHES



    . Callback , advertisement . - 200-500 allback, advertisement .





  2. CALLBACK_TYPE_FIRST_MATCH



    . Callback , advertisement .





  3. CALLBACK_TYPE_MATCH_LOST



    . Callback , advertisement advertisement . .





 CALLBACK_TYPE_ALL_MATCHES



  CALLBACK_TYPE_FIRST_MATCH



. . -  CALLBACK_TYPE_ALL_MATCHES



, callback, -  CALLBACK_TYPE_FIRST_MATCH



.





Match mode

, Android «».





  1. MATCH_MODE_AGGRESSIVE



    . advertisement .





  2. MATCH_MODE_STICKY



    . , advertisement .





,  MATCH_MODE_AGGRESSIVE



, .





Number of matches

advertisement .





  1. MATCH_NUM_ONE_ADVERTISEMENT



    . .





  2. MATCH_NUM_FEW_ADVERTISEMENT



    . .





  3. MATCH_NUM_MAX_ADVERTISEMENT



    . advertisement , .





. - , 2 .





Report delay

allback . , Android  onBatchScanResults



.  onScanResult



  . , . - , MAC- ( ).





:     Samsung S6 / Samsung S6 Edge, RSSI ( ) .





Android Bluetooth

BLE «» Bluetooth . : , MAC-, (, ), (Classic, Dual, BLE) .. Android , . , . . , Android , . - MAC- !





Bluetooth , , , 3 , :





  1. Bluetooth









  2. ( )





, , - . , Samsung, Bluetooth.





, BT . , :





// Get device object for a mac address
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(peripheralAddress)
// Check if the peripheral is cached or not
int deviceType = device.getType();
if(deviceType == BluetoothDevice.DEVICE_TYPE_UNKNOWN) {
    // The peripheral is not cached
} else {
    // The peripheral is cached
}
      
      



, , . .





?

– , , , . , BLE-, , (foreground), .





, Google () :





  • c Android 8.1  .  ScanFilters



    , Android , , .  Google.  Google.





  • c Android 7 30 , Android  SCAN_MODE_OPPORTUNISTIC



    .  , ,  30 .  commit  .





  • Android 7 5 30   .





Google . ! Android , 10 , . :





  •  StackOverflow





  •  David Young





(permissions)

, . (permissions):





<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
      
      



, , .  ACCESS_COARSE_LOCATION



 Google «» .





private boolean hasPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, ACCESS_COARSE_LOCATION_REQUEST);
            return false;
        }
    }
    return true;
}
      
      



. , BLE 2 : ACCESS_FINE_LOCATION



 ( API<23)  ACCESS_BACKGROUND_LOCATION



  Stackoverflow.





Android10:





<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
      
      



, Bluetooth, -  Intent



  :





BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (!bluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
      
      







BLE Activity (Fragment / Service), , (permissions) Android-Bluetooth . .





!








All Articles