40 votes

Appariement programmé avec un appareil BLE sous Android 4.4+.

Quelqu'un a-t-il un exemple complet et fonctionnel de la manière de coupler par programmation un appareil BLE ( no Bluetooth Classic) qui utilise la saisie par clé (c'est-à-dire un code PIN à 6 chiffres) ou la comparaison numérique sur Android 4.4 ou ultérieur ? Par "par programme", j'entends que je communique le code PIN à Android - l'utilisateur n'y est pas invité.

Il y a beaucoup de questions similaires sur SO mais elles sont soit a) à propos de Bluetooth Classic, b) anciennes (avant le setPin() y [createBond()](https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createBond()) étaient publiques), ou c) sans réponse.

Ma compréhension est la suivante.

  1. Vous vous connectez à l'appareil et découvrez ses services.
  2. Vous essayez de lire une caractéristique "protégée".
  3. Le périphérique renvoie une erreur d'authentification.
  4. Android initie en quelque sorte le jumelage et vous lui donnez le code PIN.
  5. Vous pouvez maintenant lire la caractéristique.

J'ai créé un dispositif en utilisant mBed en cours d'exécution sur le nRF51-DK et lui donner une seule caractéristique.

J'ai configuré les paramètres de sécurité comme suit :

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

Et puis dans la caractéristique j'ai utilisé

requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM);

Maintenant, quand j'essaie de le lire avec le Panneau de contrôle Nordic Master je reçois une notification de demande de jumelage comme celle-ci :

pairing request

passkey entry

Et je peux mettre ce PIN, et alors MCP dit que je suis lié, et peut lire la caractéristique.

Cependant, dans mon application, je voudrais éviter que l'utilisateur saisisse le code PIN, puisque je le connais déjà. Quelqu'un a-t-il un exemple complet et récent de la façon de procéder ?

Modifier : A propos este est la question la plus pertinente que j'ai trouvée sur SO, mais la réponse ne semble pas fonctionner.

0 votes

Le SDK Android permet-il l'appairage sans que l'utilisateur ne soit averti / ne demande de confirmation ? Il semble que vous ne puissiez pas le faire...

1 votes

Oui, c'est le cas. Il a un setPin() spécialement pour cela, et j'ai réussi à la faire fonctionner, sauf que la notification "Demande de jumelage" est toujours affichée.

0 votes

Je ne suis pas sûr que cela puisse vous aider, mais cela vaut la peine de le lire. stackoverflow.com/questions/17971834/

40voto

Timmmm Points 9909

I presque le faire fonctionner. Il s'apparie par programme, mais je n'arrive pas à faire disparaître la notification "Demande d'appariement". Certaines réponses à cette question affirment qu'il est possible de la masquer juste après son affichage à l'aide de la méthode hidden. cancelPairingUserInput() mais cela ne semble pas fonctionner pour moi.

Edit : Success !

J'ai finalement eu recours à la lecture du code source de BluetoothPairingRequest y le code qui envoie la diffusion de la demande de jumelage et j'ai réalisé que je devais intercepter la ACTION_PAIRING_REQUEST . Heureusement, il s'agit d'une diffusion d'intention ordonnée, vous pouvez donc l'intercepter avant que le système ne le fasse.

Voici la procédure.

  1. Inscrivez-vous pour recevoir BluetoothDevice.ACTION_PAIRING_REQUEST a modifié les intentions de diffusion. Utilisez une priorité élevée !
  2. Connectez-vous à l'appareil.
  3. Découvrez les services.
  4. Si vous avez déjà été déconnecté, c'est probablement parce que les informations sur les obligations sont incorrectes (par exemple, le périphérique les a purgées). Dans ce cas, supprimez les informations de liaison en utilisant une méthode cachée (sérieusement Google), et reconnectez-vous.
  5. Essayez de lire une caractéristique qui nécessite une protection MitM par chiffrement.
  6. Dans le ACTION_PAIRING_REQUEST récepteur de radiodiffusion, vérifiez que le type d'appairage est BluetoothDevice.PAIRING_VARIANT_PIN et si oui, appelez setPin() y abortBroadcast() . Sinon, vous pouvez simplement laisser le système s'en charger, ou afficher une erreur ou autre.

Voici le code.

/* This implements the BLE connection logic. Things to watch out for:

1. If the bond information is wrong (e.g. 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
                {
                    L.w("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)
        {
            L.e(e.getMessage());
        }
    }
}

0 votes

Bonjour ! Pourriez-vous dire d'où proviennent les exigences en matière de protection MITM : "Essayez de lire une caractéristique qui exige une protection MitM par cryptage" S'agit-il d'une spécification du profil GATT, du téléphone, de votre produit ?

0 votes

Elle est définie sur le produit. Chaque caractéristique GATT peut être configurée pour être l'une des suivantes ces valeurs . Je ne suis pas exactement sûr de savoir comment cela correspond à la Spécifications du noyau Bluetooth mais il y a quelques informations dans le Vol 3 Partie G Section 8, et Vol 3 Partie C Section 5.2.2.

0 votes

OK, je le vois dans le code maintenant. Avez-vous vraiment besoin d'une protection MITM ? Si le code PIN est codé en dur, la protection n'est pas très forte. La désactivation de la protection MITM devrait aboutir à l'appairage Just Works, et il ne devrait pas y avoir de dialogue sur le code PIN.

6voto

Varun A M Points 119

J'ai également été confronté au même problème et après toutes les recherches, j'ai trouvé la solution ci-dessous pour se coupler à un BLE sans aucune intervention manuelle.

(Testé et fonctionnant ! !!)

Je cherche essentiellement un appareil Bluetooth particulier (je connais l'adresse MAC) et je l'apparie une fois qu'il est trouvé. La première chose à faire est de créer une demande de jumelage en utilisant un récepteur de diffusion et de traiter la demande comme ci-dessous.

IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
                intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
                registerReceiver(broadCastReceiver,intentFilter);

Vous devez écrire le broadcastReceiver et le gérer comme ci-dessous.

String BLE_PIN = "1234"
private BroadcastReceiver broadCastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if(BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action))
        {
            BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            bluetoothDevice.setPin(BLE_PIN.getBytes());
            Log.e(TAG,"Auto-entering pin: " + BLE_PIN);
            bluetoothDevice.createBond();
            Log.e(TAG,"pin entered and request sent...");
        }
    }
};

Voilà ! Vous devriez être en mesure d'apparier votre appareil Bluetooth sans AUCUNE INTERVENTION MANUELLE.

J'espère que cela vous aidera :-) S'il vous plaît, faites la bonne réponse si cela fonctionne pour vous.

0 votes

Cela semble être exactement la même chose que ma réponse.

2 votes

Je l'ai juste simplifié et ajouté pour éviter la confusion des gens.

4 votes

Ok, vous devriez probablement le dire alors, et expliquer les changements, par exemple en supprimant les éléments suivants abortBroadcast() et en ajoutant createBond() .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X