Connecter appareils Bluetooth

Pour créer une connexion entre deux appareils, vous devez mettre en œuvre les mécanismes côté serveur et côté client, car un appareil doit ouvrir un socket serveur, et l'autre doit établir la connexion à l'aide de son adresse MAC. L'appareil serveur et l'appareil client obtiennent chacun le BluetoothSocket requis de différentes manières. Le serveur reçoit les informations de socket lorsqu'une connexion entrante est acceptée. Le client fournit des informations sur le socket lorsqu'il ouvre un canal RFCOMM sur le serveur.

Le serveur et le client sont considérés comme connectés l'un à l'autre lorsqu'ils ont chacun un BluetoothSocket connecté sur le même canal RFCOMM. À ce stade, chaque appareil peut obtenir des flux d'entrée et de sortie, et le transfert de données peut commencer. Ce processus est abordé dans la section sur le transfert de données Bluetooth. Cette section explique comment établir la connexion entre deux appareils.

Assurez-vous de disposer des autorisations Bluetooth appropriées et configurez votre application pour le Bluetooth avant d'essayer de trouver des appareils Bluetooth.

Techniques de connexion

Une technique de mise en œuvre consiste à préparer automatiquement chaque appareil en tant que serveur, afin que chaque appareil ait un socket serveur ouvert et écoute les connexions. Dans ce cas, l'un ou l'autre des appareils peut établir une connexion avec l'autre et devenir le client. Un appareil peut également héberger explicitement la connexion et ouvrir un socket de serveur à la demande, et l'autre appareil lance la connexion.


Figure 1 : Boîte de dialogue d'association Bluetooth

Se connecter en tant que serveur

Lorsque vous souhaitez connecter deux appareils, l'un d'eux doit agir en tant que serveur en maintenant un élément BluetoothServerSocket ouvert. L'objectif du socket serveur est d'écouter les requêtes de connexion entrantes et de fournir un BluetoothSocket connecté une fois la requête acceptée. Lorsque le BluetoothSocket est obtenu à partir de BluetoothServerSocket, le BluetoothServerSocket peut et doit être supprimé, sauf si vous souhaitez que l'appareil accepte davantage de connexions.

Pour configurer un socket de serveur et accepter une connexion, procédez comme suit:

  1. Obtenez un BluetoothServerSocket en appelant listenUsingRfcommWithServiceRecord(String, UUID).

    La chaîne est un nom identifiable de votre service, que le système écrit automatiquement dans une nouvelle entrée de base de données du protocole de détection de service (SDP) sur l'appareil. Ce nom est arbitraire et peut simplement correspondre au nom de votre application. L'identifiant unique universel (UUID) est également inclus dans l'entrée SDP et constitue la base de l'accord de connexion avec l'appareil client. Autrement dit, lorsque le client tente de se connecter à cet appareil, il contient un UUID qui identifie de manière unique le service auquel il souhaite se connecter. Ces UUID doivent correspondre pour que la connexion soit acceptée.

    Un UUID est un format standardisé de 128 bits qui correspond à un ID de chaîne permettant d'identifier de manière unique des informations. Un UUID est utilisé pour identifier des informations qui doivent être uniques dans un système ou un réseau, car la probabilité qu'un UUID soit répété est effectivement nulle. Il est généré indépendamment, sans l'utilisation d'une autorité centralisée. Dans ce cas, elle est utilisée pour identifier de manière unique le service Bluetooth de votre application. Pour obtenir un UUID à utiliser avec votre application, vous pouvez utiliser l'un des nombreux générateurs aléatoires UUID du Web, puis initialiser un UUID avec fromString(String).

  2. Commencez à écouter les demandes de connexion en appelant accept().

    Il s'agit d'un appel bloquant. Il est renvoyé lorsqu'une connexion a été acceptée ou qu'une exception s'est produite. Une connexion n'est acceptée que lorsqu'un appareil distant a envoyé une requête de connexion contenant un UUID correspondant à celui enregistré avec ce socket du serveur d'écoute. En cas de succès, accept() renvoie un BluetoothSocket connecté.

  3. Si vous ne souhaitez pas accepter de connexions supplémentaires, appelez close().

    Cet appel de méthode libère le socket du serveur et toutes ses ressources, mais ne ferme pas le BluetoothSocket connecté renvoyé par accept(). Contrairement à TCP/IP, RFCOMM n'autorise qu'un seul client connecté par canal à la fois. Dans la plupart des cas, il est donc logique d'appeler close() sur BluetoothServerSocket immédiatement après avoir accepté un socket connecté.

Étant donné que l'appel accept() est un appel bloquant, ne l'exécutez pas dans le thread UI de l'activité principale. Son exécution dans un autre thread garantit que votre application peut toujours répondre à d'autres interactions utilisateur. Il est généralement judicieux d'effectuer toutes les tâches impliquant un BluetoothServerSocket ou un BluetoothSocket dans un nouveau thread géré par votre application. Pour annuler un appel bloqué tel que accept(), appelez close() sur BluetoothServerSocket ou BluetoothSocket à partir d'un autre thread. Notez que toutes les méthodes sur BluetoothServerSocket ou BluetoothSocket sont thread-safe.

Exemple

Voici un thread simplifié pour le composant serveur qui accepte les connexions entrantes:

Kotlin

private inner class AcceptThread : Thread() {

   private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {
       bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID)
   }

   override fun run() {
       // Keep listening until exception occurs or a socket is returned.
       var shouldLoop = true
       while (shouldLoop) {
           val socket: BluetoothSocket? = try {
               mmServerSocket?.accept()
           } catch (e: IOException) {
               Log.e(TAG, "Socket's accept() method failed", e)
               shouldLoop = false
               null
           }
           socket?.also {
               manageMyConnectedSocket(it)
               mmServerSocket?.close()
               shouldLoop = false
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   fun cancel() {
       try {
           mmServerSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the connect socket", e)
       }
   }
}

Java

private class AcceptThread extends Thread {
   private final BluetoothServerSocket mmServerSocket;

   public AcceptThread() {
       // Use a temporary object that is later assigned to mmServerSocket
       // because mmServerSocket is final.
       BluetoothServerSocket tmp = null;
       try {
           // MY_UUID is the app's UUID string, also used by the client code.
           tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's listen() method failed", e);
       }
       mmServerSocket = tmp;
   }

   public void run() {
       BluetoothSocket socket = null;
       // Keep listening until exception occurs or a socket is returned.
       while (true) {
           try {
               socket = mmServerSocket.accept();
           } catch (IOException e) {
               Log.e(TAG, "Socket's accept() method failed", e);
               break;
           }

           if (socket != null) {
               // A connection was accepted. Perform work associated with
               // the connection in a separate thread.
               manageMyConnectedSocket(socket);
               mmServerSocket.close();
               break;
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   public void cancel() {
       try {
           mmServerSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the connect socket", e);
       }
   }
}

Dans cet exemple, une seule connexion entrante est souhaitée. Par conséquent, dès qu'une connexion est acceptée et que l'BluetoothSocket est acquise, l'application transmet l'BluetoothSocket acquise à un thread distinct, ferme l'BluetoothServerSocket et sort de la boucle.

Notez que lorsque accept() renvoie BluetoothSocket, le socket est déjà connecté. Vous ne devez donc pas appeler connect(), comme c'est le cas côté client.

La méthode manageMyConnectedSocket() spécifique à l'application est conçue pour lancer le thread de transfert de données. Ce processus est abordé dans l'article Transférer des données Bluetooth.

En règle générale, vous devez fermer votre BluetoothServerSocket dès que vous avez terminé d'écouter les connexions entrantes. Dans cet exemple, close() est appelé dès que BluetoothSocket est acquis. Vous pouvez également fournir dans votre thread une méthode publique permettant de fermer le BluetoothSocket privé si vous devez arrêter d'écouter sur ce socket de serveur.

Se connecter en tant que client

Pour établir une connexion avec un appareil distant qui accepte les connexions sur un socket de serveur ouvert, vous devez d'abord obtenir un objet BluetoothDevice qui représente l'appareil distant. Pour savoir comment créer un BluetoothDevice, consultez Rechercher des appareils Bluetooth. Vous devez ensuite utiliser BluetoothDevice pour acquérir un BluetoothSocket et établir la connexion.

La procédure de base est la suivante:

  1. À l'aide de BluetoothDevice, obtenez un BluetoothSocket en appelant createRfcommSocketToServiceRecord(UUID).

    Cette méthode initialise un objet BluetoothSocket qui permet au client de se connecter à un BluetoothDevice. L'UUID transmis ici doit correspondre à l'UUID utilisé par le périphérique serveur lorsqu'il a appelé listenUsingRfcommWithServiceRecord(String, UUID) pour ouvrir son BluetoothServerSocket. Pour utiliser un UUID correspondant, codez en dur la chaîne UUID dans votre application, puis référencez-la à partir du code du serveur et du code client.

  2. Démarrez la connexion en appelant connect(). Notez que cette méthode est un appel bloquant.

    Une fois qu'un client a appelé cette méthode, le système effectue une recherche SDP pour trouver l'appareil distant avec l'UUID correspondant. Si la recherche aboutit et que l'appareil distant accepte la connexion, il partage le canal RFCOMM à utiliser pendant la connexion, et la méthode connect() est renvoyée. Si la connexion échoue ou si la méthode connect() expire (après environ 12 secondes), elle génère une erreur IOException.

Comme connect() est un appel bloquant, vous devez toujours effectuer cette procédure de connexion dans un thread distinct du thread d'activité principal (UI).

Exemple

Voici un exemple de base de thread client qui initie une connexion Bluetooth:

Kotlin

private inner class ConnectThread(device: BluetoothDevice) : Thread() {

   private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
       device.createRfcommSocketToServiceRecord(MY_UUID)
   }

   public override fun run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter?.cancelDiscovery()

       mmSocket?.let { socket ->
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           socket.connect()

           // The connection attempt succeeded. Perform work associated with
           // the connection in a separate thread.
           manageMyConnectedSocket(socket)
       }
   }

   // Closes the client socket and causes the thread to finish.
   fun cancel() {
       try {
           mmSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the client socket", e)
       }
   }
}

Java

private class ConnectThread extends Thread {
   private final BluetoothSocket mmSocket;
   private final BluetoothDevice mmDevice;

   public ConnectThread(BluetoothDevice device) {
       // Use a temporary object that is later assigned to mmSocket
       // because mmSocket is final.
       BluetoothSocket tmp = null;
       mmDevice = device;

       try {
           // Get a BluetoothSocket to connect with the given BluetoothDevice.
           // MY_UUID is the app's UUID string, also used in the server code.
           tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's create() method failed", e);
       }
       mmSocket = tmp;
   }

   public void run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter.cancelDiscovery();

       try {
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           mmSocket.connect();
       } catch (IOException connectException) {
           // Unable to connect; close the socket and return.
           try {
               mmSocket.close();
           } catch (IOException closeException) {
               Log.e(TAG, "Could not close the client socket", closeException);
           }
           return;
       }

       // The connection attempt succeeded. Perform work associated with
       // the connection in a separate thread.
       manageMyConnectedSocket(mmSocket);
   }

   // Closes the client socket and causes the thread to finish.
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the client socket", e);
       }
   }
}

Notez que dans cet extrait, cancelDiscovery() est appelé avant la tentative de connexion. Vous devez toujours appeler cancelDiscovery() avant connect(), en particulier parce que cancelDiscovery() réussit, que la découverte de l'appareil soit en cours ou non. Si votre application doit déterminer si la détection d'appareils est en cours, vous pouvez le faire à l'aide de isDiscovering().

La méthode manageMyConnectedSocket() spécifique à l'application est conçue pour lancer le thread de transfert de données, comme expliqué dans la section sur le transfert de données Bluetooth.

Lorsque vous avez terminé d'utiliser votre BluetoothSocket, appelez toujours close(). Cela entraîne la fermeture immédiate du socket connecté et la libération de toutes les ressources internes associées.