Accodamento programmaticamente con un dispositivo BLE in Android 4.4+

Qualcuno ha un esempio completo di come associarsi in modo programmato con un dispositivo BLE ( non Bluetooth Classic) che utilizza la voce di accesso (ad esempio un PIN a 6 cifre) o un confronto numbersco su Android 4.4 o versione successiva? Da 'programmaticamente' intendo dire a Android il PIN – l'utente non viene richiesto.

Ci sono molte domande simili su questo su SO, ma sono o) a proposito di Bluetooth Classic, b) vecchi (prima che setPin() e createBond() stati pubblici), oppure c) senza risposta.

  • come progettare layout lineare da destra a sinistra
  • "R non può essere risolto a una variabile"?
  • SSL rotto pipe
  • Classe astratta come parcelable
  • Invito Firebase non funziona in APK di creazione di rilascio
  • Come / dovrei implementare un filter Kalman per get dati Accelerometro accurate?
  • La mia comprensione è la seguente.

    1. Si connette al dispositivo e scopri i suoi servizi.
    2. Si tenta di leggere una caratteristica 'protetta'.
    3. Il dispositivo restituisce un errore di authentication.
    4. Android inizia in qualche modo l'accoppiamento e gli dici il PIN.
    5. Ora è ansible leggere la caratteristica.

    Ho creato un dispositivo che utilizza mBed in esecuzione sul nRF51-DK e ha dato una singola caratteristica.

    Ho impostato i parametri di sicurezza così:

     ble.securityManager().init( true, // Enable bonding (though I don't really need this) true, // Require MitM protection. I assume you don't get a PIN prompt without this, though I'm not 100% sure. SecurityManager::IO_CAPS_DISPLAY_ONLY, // This makes it us the Passkey Entry (PIN) pairing method. "123456"); // Static PIN 

    E poi nella caratteristica che ho usato

     requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); 

    Ora, quando cerco di leggerlo con il Nordic Master Control Panel , ottengo una notifica di richiesta di abbinamento come questo:

    richiesta di abbinamento

    entrata passkey

    E posso mettere questo PIN, e poi MCP dice che sono legato e riesco a leggere la caratteristica.

    Tuttavia, nella mia app vorrei evitare che l'utente inserisse il PIN, dato che lo conosco già. Qualcuno ha un esempio completo di come farlo?

    Modifica : A proposito, questa è la domanda più rilevante che ho trovato su SO, ma la risposta non sembra funzionare.

  • Pausa Bluetooth Bluetooth senza utente Inserisci pin e conferma utilizzando l'API Android
  • One Solution collect form web for “Accodamento programmaticamente con un dispositivo BLE in Android 4.4+”

    Ho quasi funzionato. Essa si accoppia in modo programmato, ma non riesco a liberarsi della notifica di "richiesta di accoppiamento". Alcune risposte a questa domanda sostengono di essere in grado di hide appena dopo che viene mostrato utilizzando il metodo cancelPairingUserInput() nascosto ma che non sembra funzionare per me.

    Modifica: successo!

    Ho finalmente ricorso a leggere il codice sorgente di BluetoothPairingRequest e il codice che invia la richiesta di abbinamento trasmesso e realizzato che dovrei intercettare ACTION_PAIRING_REQUEST . Fortunatamente è una trasmissione ordinata di intenti in modo da poter intercettarla prima che il sistema faccia.

    Ecco la procedura.

    1. Registra per ricevere BluetoothDevice.ACTION_PAIRING_REQUEST intenti di trasmissione modificati. Utilizza un'alta priorità!
    2. Collegarsi al dispositivo.
    3. Scopri i servizi.
    4. Se hai scollegato ormai, probabilmente è perché le informazioni dei legami non sono corrette (per esempio la periferica lo ha spurgato). In tal caso, eliminare le informazioni sui titoli utilizzando un metodo nascosto (seriamente Google) e ricollegarlo.
    5. Provare a leggere una caratteristica che richiede la protezione di crittografia MitM.
    6. Nel ricevitore broadcast ACTION_PAIRING_REQUEST , verificare che il tipo di associazione sia BluetoothDevice.PAIRING_VARIANT_PIN e, in caso affermativo, call setPin() e abortBroadcast() . Altrimenti puoi lasciare che il sistema gestisca, o mostri un errore o qualsiasi altra cosa.

    Ecco il codice.

     /* This implements the BLE connection logic. Things to watch out for: 1. If the bond information is wrong (eg it has been deleted on the peripheral) then discoverServices() will cause a disconnect. You need to delete the bonding information and reconnect. 2. If the user ignores the PIN request, you get the undocumented GATT_AUTH_FAILED code. */ public class ConnectActivityLogic extends Fragment { // The connection to the device, if we are connected. private BluetoothGatt mGatt; // This is used to allow GUI fragments to subscribe to state change notifications. public static class StateObservable extends Observable { private void notifyChanged() { setChanged(); notifyObservers(); } }; // When the logic state changes, State.notifyObservers(this) is called. public final StateObservable State = new StateObservable(); public ConnectActivityLogic() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Tell the framework to try to keep this fragment around // during a configuration change. setRetainInstance(true); // Actually set it in response to ACTION_PAIRING_REQUEST. final IntentFilter pairingRequestFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); pairingRequestFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1); getActivity().getApplicationContext().registerReceiver(mPairingRequestRecevier, pairingRequestFilter); // Update the UI. State.notifyChanged(); // Note that we don't actually need to request permission - all apps get BLUETOOTH and BLUETOOTH_ADMIN permissions. // LOCATION_COARSE is only used for scanning which I don't need (MAC is hard-coded). // Connect to the device. connectGatt(); } @Override public void onDestroy() { super.onDestroy(); // Disconnect from the device if we're still connected. disconnectGatt(); // Unregister the broadcast receiver. getActivity().getApplicationContext().unregisterReceiver(mPairingRequestRecevier); } // The state used by the UI to show connection progress. public ConnectionState getConnectionState() { return mState; } // Internal state machine. public enum ConnectionState { IDLE, CONNECT_GATT, DISCOVER_SERVICES, READ_CHARACTERISTIC, FAILED, SUCCEEDED, } private ConnectionState mState = ConnectionState.IDLE; // When this fragment is created it is given the MAC address and PIN to connect to. public byte[] macAddress() { return getArguments().getByteArray("mac"); } public int pinCode() { return getArguments().getInt("pin", -1); } // Start the connection process. private void connectGatt() { // Disconnect if we are already connected. disconnectGatt(); // Update state. mState = ConnectionState.CONNECT_GATT; State.notifyChanged(); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress()); // Connect! mGatt = device.connectGatt(getActivity(), false, mBleCallback); } private void disconnectGatt() { if (mGatt != null) { mGatt.disconnect(); mGatt.close(); mGatt = null; } } // See https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/stack/include/gatt_api.h private static final int GATT_ERROR = 0x85; private static final int GATT_AUTH_FAIL = 0x89; private android.bluetooth.BluetoothGattCallback mBleCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); switch (newState) { case BluetoothProfile.STATE_CONNECTED: // Connected to the device. Try to discover services. if (gatt.discoverServices()) { // Update state. mState = ConnectionState.DISCOVER_SERVICES; State.notifyChanged(); } else { // Couldn't discover services for some reason. Fail. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } break; case BluetoothProfile.STATE_DISCONNECTED: // If we try to discover services while bonded it seems to disconnect. // We need to debond and rebond... switch (mState) { case IDLE: // Do nothing in this case. break; case CONNECT_GATT: // This can happen if the bond information is incorrect. Delete it and reconnect. deleteBondInformation(gatt.getDevice()); connectGatt(); break; case DISCOVER_SERVICES: // This can also happen if the bond information is incorrect. Delete it and reconnect. deleteBondInformation(gatt.getDevice()); connectGatt(); break; case READ_CHARACTERISTIC: // Disconnected while reading the characteristic. Probably just a link failure. gatt.close(); mState = ConnectionState.FAILED; State.notifyChanged(); break; case FAILED: case SUCCEEDED: // Normal disconnection. break; } break; } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); // Services have been discovered. Now I try to read a characteristic that requires MitM protection. // This triggers pairing and bonding. BluetoothGattService nameService = gatt.getService(UUIDs.NAME_SERVICE); if (nameService == null) { // Service not found. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); return; } BluetoothGattCharacteristic characteristic = nameService.getCharacteristic(UUIDs.NAME_CHARACTERISTIC); if (characteristic == null) { // Characteristic not found. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); return; } // Read the characteristic. gatt.readCharacteristic(characteristic); mState = ConnectionState.READ_CHARACTERISTIC; State.notifyChanged(); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { // Characteristic read. Check it is the right one. if (!UUIDs.NAME_CHARACTERISTIC.equals(characteristic.getUuid())) { // Read the wrong characteristic. This shouldn't happen. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); return; } // Get the name (the characteristic I am reading just contains the device name). byte[] value = characteristic.getValue(); if (value == null) { // Hmm... } disconnectGatt(); mState = ConnectionState.SUCCEEDED; State.notifyChanged(); // Success! Save it to the database or whatever... } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { // This is where the tricky part comes if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { // Bonding required. // The broadcast receiver should be called. } else { // ? } } else if (status == GATT_AUTH_FAIL) { // This can happen because the user ignored the pairing request notification for too long. // Or presumably if they put the wrong PIN in. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } else if (status == GATT_ERROR) { // I thought this happened if the bond information was wrong, but now I'm not sure. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } else { // That's weird. disconnectGatt(); mState = ConnectionState.FAILED; State.notifyChanged(); } } }; private final BroadcastReceiver mPairingRequestRecevier = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); if (type == BluetoothDevice.PAIRING_VARIANT_PIN) { device.setPin(Util.IntToPasskey(pinCode())); abortBroadcast(); } else { Lw("Unexpected pairing type: " + type); } } } }; public static void deleteBondInformation(BluetoothDevice device) { try { // FFS Google, just unhide the method. Method m = device.getClass().getMethod("removeBond", (Class[]) null); m.invoke(device, (Object[]) null); } catch (Exception e) { Le(e.getMessage()); } } } 
    L'Android è un fan Android di Google, tutto su telefoni Android, Android Wear, Android Dev e applicazioni Android Games e così via.