Réaliser une liaison Bluetooth entre un Microcontrôleur et Android
24 avril 2011
Voici une méthode pour faire communiquer un microcontrôleur avec un smartphone Android pour seulement quelques euros.La liaison Bluetooth se fait par le profil Bluetooth SPP (Serial Port Profile), qui permet d'émuler une connexion série.
Cette méthode est donc compatible avec n'importe quel microcontrôleur ayant une interface série (PIC, MSP430, Atmel AVR (Arduino), ...).
Matériel
Votre smartphone (ou tout autre dispositif sous Android) doit avoir le bluetooth bien évidement et doit tourner sur Android 2.0 minimum :
les fonctions pour l'utilisation bluetooth ne sont présente dans le SDK qu'à partir de l'API Level 5.Le microcontrôleur doit pouvoir communiquer par une liaison série, aussi appelée RS232, ou UART. Quelques exemples de microcontrôleur : PIC18F2550, PIC18F14K50, PIC18F1320, PIC16F886, ... ou bien chez TI : MSP430G2231, MSP430G2452, ...
![Exemple de module bluetooth](images/andro_bt/modulebluetooth.jpg)
Attention, selon votre microcontrôleur et le module que vous choisissez, vous aurez peut être besoin d'une interface du type MAX232 afin de rendre les tensions compatibles entre elles. Le schéma de droite est un exemple pour interfacer un module bluetooth prévu pour une connexion série sur un PC (prise DB9) qui envoie un signal entre -10V et 10V et un PIC (signaux 0-5V).
Vous pourrez en trouvez à partir de 5€ sur eBay (en recherchant 'module bluetooth rs232'), SparkFun, Lextronic et d'autres revendeurs.
Du côté du microcontrôleur
![Un Module Bluetooth relié à un MSP430G2231](images/andro_bt/mps430bluetooth.jpg)
Il faut ensuite configurer le microcontrôleur et/ou le module bluetooth afin qu'ils utilisent la même vitesse de connexion, le même baud rate.
Cette vitesse n'intervient pas pour la suite (pour Android) car la liaison bluetooth entre le module et Android se fait par le protocole RFCOMM, qui va beaucoup plus vite que la liaison série. Le module bluetooth se charge de faire la 'conversion' entre ces deux protocoles. Vous pouvez donc choisir le baud rate que vous voulez.
Il n'y a pas de règle spéciale / de protocole pour l'envoi ou la réception des données pour le microcontrôleur, il suffit d'envoyer les données simplement, comme pour une liaison série.
Le programme Android
![Android](images/andro_bt/android.png)
![Interface Android-Bluetooth-Microcontrôleur](images/andro_bt/bluetooth_android.png)
Prérequis
Il vous faudra ajouter les permissions BLUETOOTH et BLUETOOTH_ADMIN afin de pouvoir utiliser le bluetooth dans votre application. BLUETOOTH_ADMIN n'est nécessaire que pour la découverte, l'association de périphériques et l'activation/désactivation du bluetooth.Ajoutez ceci dans l'AndroidManifest.xml :
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" />
3 classes spécifiques du SDK sont nécessaires :
- BluetoothDevice : Classe qui représente un périphérique bluetooth
- BluetoothAdapter : Classe qui représente le module bluetooth de votre Android, elle permet de scanner les périphériques présents, d'activer / désactiver le bluetooth, ...
- BluetoothSocket : Classe qui permet d'obtenir les canaux d'écriture (émission) et de lecture (réception) du périphérique
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket;
La classe va donc contenir le 'périphérique', sa 'connexion', et ses canaux d'entrée et sortie :
public class BtInterface { private BluetoothDevice device = null;// le périphérique (le module bluetooth) private BluetoothSocket socket = null; private InputStream receiveStream = null;// Canal de réception private OutputStream sendStream = null;// Canal d'émission
Première étape : récupérer le BluetoothDevice de notre périphérique, pour cela, on utilise la fonction getBondedDevices() de BluetoothAdapter qui permet d'obtenir la liste des périphériques associés à Android. On récupère le module bluetooth par défaut d'Android avec la fonction BluetoothAdapter.getDefaultAdapter().
Il suffit de chercher dans cette liste notre périphérique. On récupère ensuite le BluetoothSocket de notre périphérique avec la fonction createRfcommSocketToServiceRecord(UUID uuid); Cette fonction prend en paramètre un UUID, on utilise ici l'UUID 00001101-0000-1000-8000-00805F9B34FB, qui indique une connexion 'port série'.
Les canaux d'émission/réception sont ensuite récupérés avec les fonctions getInputStream et getOutputStream.
Le module bluetooth n'a pas besoin d'être sous tension pour utiliser ces fonctions, les canaux seront seulement invalides tant que celui-ci n'est pas connecté.
public BtInterface() { // On récupère la liste des périphériques associés Set<BluetoothDevice> setpairedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices(); BluetoothDevice[] pairedDevices = (BluetoothDevice[]) setpairedDevices.toArray(new BluetoothDevice[setpairedDevices.size()]); // On parcourt la liste pour trouver notre module bluetooth for(int i=0;i<pairedDevices.length;i++) { // On teste si ce périphérique contient le nom du module bluetooth connecté au microcontrôleur if(pairedDevices[i].getName().contains("ModuleBluetooth")) { device = pairedDevices[i]; try { // On récupère le socket de notre périphérique socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); receiveStream = socket.getInputStream();// Canal de réception (valide uniquement après la connexion) sendStream = socket.getOutputStream();// Canal d'émission (valide uniquement après la connexion) } catch (IOException e) { e.printStackTrace(); } break; } } }
Connexion
Pour se connecter, on utilise la fonction connect(); de BluetoothSocket. La connexion pouvant prendre du temps, il est nécessaire de la lancer dans un autre thread pour éviter de bloquer l'application.public void connect() { new Thread() { @Override public void run() { try { socket.connect();// Tentative de connexion // Connexion réussie } catch (IOException e) { // Echec de la connexion e.printStackTrace(); } } }.start(); }
Envoi de données
Pour envoyer des données, on va tout simplement utiliser les fonctions write(...) de OutputStreampublic void sendData(String data) { try { // On écrit les données dans le buffer d'envoi sendStream.write(data.getBytes()); // On s'assure qu'elles soient bien envoyées sendStream.flush(); } catch (IOException e) { e.printStackTrace(); } }
Réception de données
Pour recevoir des données, le plus simple est de faire un Thread qui va vérifier l'arrivé de données en permanence. On utilise les fonctions read(...) et available() de InputStream pour lire ces données et vérifier si de nouvelles sont disponibles.Problème : on ne peut afficher les données depuis ce nouveau thread : Android impose de les envoyer dans le thread de l'UI. On utilise donc la classe Handler. On doit alors modifier le constructeur pour récupérer un Handler du thread de l'UI.
private ReceiverThread receiverThread; public BtInterface(Handler h) { ... receiverThread = new ReceiverThread(h); // On crée le thread de réception des données avec l'Handler venant du thread UI ... } public void connect() { new Thread() { @Override public void run() { try { socket.connect();// Tentative de connexion // Connexion réussie receiverThread.start(); // On démarre la vérification des données } catch (IOException e) { // Echec de la connexion e.printStackTrace(); } } }.start(); } private class ReceiverThread extends Thread { Handler handler; ReceiverThread(Handler h) { handler = h; } @Override public void run() { while(true) { try { // On teste si des données sont disponibles if(receiveStream.available() > 0) { byte buffer[] = new byte[100]; // On lit les données, k représente le nombre de bytes lu int k = receiveStream.read(buffer, 0, 100); if(k > 0) { // On convertit les données en String byte rawdata[] = new byte[k]; for(int i=0;i<k;i++) rawdata[i] = buffer[i]; String data = new String(rawdata); // On envoie les données dans le thread de l'UI pour les afficher Message msg = handler.obtainMessage(); Bundle b = new Bundle(); b.putString("receivedData", data); msg.setData(b); handler.sendMessage(msg); } } } catch (IOException e) { e.printStackTrace(); } } } }
Et dans le thread de l'UI, il suffit de créer un Handler et de le faire passer dans le constructeur de notre classe.
final Handler handler = new Handler() { public void handleMessage(Message msg) { String data = msg.getData().getString("receivedData"); ... // Affichage de data } };
Déconnexion
Enfin, pour fermer la connexion, il y a la fonction close() de BluetoothSocket :public void close() { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } }