mercredi 20 décembre 2017

HC-06 à 115200 Bauds entre Arduino et Android

Il existe de très nombreux articles sur internet, relatifs aux modules HC-05 et HC-06. Tous les exemples fonctionnent ou du moins semblent fonctionner. Tant que vous vous contentez d'allumer une Led ou de réagir à l'appui sur un petit bouton, ça va. Quand vous commencez à vouloir paramétrer les modules, augmenter la vitesse, transmettre pas mal de données, les problèmes commencent à arriver.

Pire, la réalisation d'un bon système de communication nécessitant tout un tas de réglages, il n'y a généralement pas un point qui ne marche pas, mais au contraire une succession de micro détails plus ou moins défaillants, sans nécessairement de lien les uns avec les autres.
Ayant eu à réaliser une application avec communication rapide et obligation de haute fiabilité entre un système de type Arduino et des applications Android, j'ai été obligé de chercher ce qui se passait afin de résoudre tout ces petits détails.
Cet article va donc tenter de vous aider, non pas en vous donnant du code tout fait, mais en vous donnant des explications. Ainsi, si d'autres problèmes surviennent, vous saurez comment analyser et vous saurez certainement les résoudre.

HC-05/ HC06

Ces sont les deux modules les plus fréquemment utilisés sur Arduino. On les trouve facilement en boutique et ils ne sont pas très chers. Ils ont plusieurs différences, certaines évidentes, d'autres beaucoup moins.

iPhone

Le premier point c'est que ces modules ne sont pas compatibles avec les iPhone. Si votre application doit communiquer avec un appareil Apple, il vous faut (à priori) un module BLE (Bluetooth Low Energy) donc par exemple un module HC-08.

Esclave vs maitre-esclave

Le HC-05 est un module maitre-esclave alors que le HC-06 est un module exclusivement esclave. Un module maitre est capable de détecter les autres modules et de leur demander l'autorisation de communiquer. Un module seulement esclave ne peut que répondre aux demandes des modules maitres.
Votre smartphone a un module maitre-esclave: il peut détecter les autres modules (donc il peut fonctionner en maitre) et peut aussi être détecté par les autres (donc il peut fonctionner en esclave).
Si vous voulez faire un système Arduino qui reçoit ou envoi des données depuis un smartphone, prenez un module HC-06 donc seulement esclave puisque la détection du module se fera sur le smartphone.
Par contre si vous voulez faire communiquer deux Arduino ensemble, l'un des deux devra avoir un HC-05 (cet Arduino sera alors l'initiateur de la connexion puisqu'il aura un module maitre esclave) et un HC-06 (sur l'Arduino qui sera esclave).

Distance de communication

Nous parlons ici de modules Bluetooth. La communication se fait sur quelques mètres, pas plus. Si vous voulez communiquer sur une plus grande distance, jetez un oeil du côté des modules Xbee, ou bien du côté du module HC-12. Concernant le HC-12, celui-ci ressemble beaucoup aux modules HC-05 et HC-06 (au niveau visuel et au niveau du nom!) mais, contrairement à ce que l'on trouve indiqué sur certaines boutiques, ce n'est pas un module Bluetooth! Il ne sera donc pas visible par votre Smartphone, ni pas les HC-05. pour faire communiquer un Arduino équipé d'un HC-12 avec un appareil Android situé par exemple à 500m, il vous faudra l'Arduino distant, mais également un "pont", proche de votre Android, pont qui aura un HC-12 et un HC-06 (pour faire le relais entre le HC-12 distant et le Bluetooth).

Paramètrage

Les HC-05 et HC-06 sont paramétrables avec des commandes Hayes, également appelées commandes AT. Les HC-05 et HC-06 ne reconnaissent pas exactement les mêmes commandes mais surtout, ne les reçoivent pas de la même manière.

Les commandes AT

Elles sont composées de lettres et commençent toutes par "AT" donc A majuscule et T majuscule. On envoi ces commandes aux modules avec un appel à Serial.print() et on lit le résultat avec un classique Serial.available() suivi d'un Serial.read().

Sauf qu'il y a un petit détail...
Intéressons nous à 2 commandes afin de comprendre:
  • La commande "AT" permet de savoir si le module est présent. Si oui, il répondra "OK"
  • La commande "AT+NAMEXXXXX" permet de changer le nom du module, XXXXX représentant le nouveau nom. C'est ce nom qui apparait sur votre Smartphone quand vous demandez la liste des appareils Bluetooth.

Maintenant imaginons que je veuille renommer mon module "PATRICK". Je vais donc lui envoyer AT+NAMEPATRICK. Le problème c'est que dans "PATRICK" il y a "AT". La question est donc de savoir si le module va croire que j'envoi "AT+NAMEPATRICK" ou s'il va croire que j'envoi "AT+NAMEP" puis "AT" donc deux commandes. Dans le premier cas mon module sera bien renommé "PATRICK" mais dans le second il sera simplement renommé "P".

Dans le cas du HC-05, les commandes "AT" doivent se terminer avec un retour à la ligne. On pourra donc les envoyer avec Serial.println() ce qui rajoutera le retour à la ligne en fin de commande. Ce "retour à la ligne" c'est donc la solution pour séparer les commandes. Avec le HC-05 il n'y a donc pas de risque d'avoir le module nommé "P" à la place de "PATRICK".
Mais dans le cas du HC-06, ça se complique car il n'y a pas de code de séparation! En fait, c'est le temps qui s'écoule entre la réception de chaque caractère qui fait que le HC-06 décide que la commande est finie ou non... D'après la Datasheet du HC-06, ce temps limite est de 1 sec.
En clair, si vous écrivez:

Serial.print("AT+NAMEPATRICK");
Serial.print("AT");

votre HC-06 se nommera "PATRICKAT" car vous n'avez pas laissé de temps entre la première et la seconde commande donc le HC-06 va considérer que c'est une seule et même commande.
Ce qu'il faut c'est donc laisser un peu de temps. Pour ma part, je laisse 2 secondes pour être plus sûr.
Donc:

Serial.print("AT+NAMEPATRICK");
delay(2000);
Serial.print("AT");

Là, votre module sera nommé "PATRICK" et la seconde commande vous retournera "OK".

Le port Série

Certains articles vous proposent de brancher votre HC-06 et de la paramétrer en tapant les commandes sur le port série. Sauf que là encore des fois ça marche, des fois pas. J'ai ainsi trouvé un exemple qui lisait les données du Terminal Serie, qui renvoyait pour test ces caractères vers le Terminal et les envoyait aussi vers le HC-06 puis qui affichait sur le Terminal ce qui était reçu du HC-06. Le tout à 9600 bauds donc lentement.

Le résultat était étrange: quand on envoyait "AT+VERSION" à partir du Terminal, donc un ordre censé retourné le numéro de version du module, on voyait "AT+OVKERSION" s'afficher sur le Terminal! Là encore, problème de vitesse: la transmission étant lente, dès que le module avait reçu "AT", comme il s'écoulait du temps entre les envois, le module ne voyait rien d'autre arriver et déduisait que la commande est finie.  Il avait donc reçu "AT", et répondait "OK". Tout se faisant lentement, on avait le temps d'envoyer le "+" puis on recevait le "O" de "OK", on envoyait le "V" de "VERSION", puis on recevait le "K" de "OK" etc... Tout était mélangé.
Comment faire? d'abord il ne faut pas oublier de mettre votre Terminal en mode "pas de fin de fin de ligne" (pour le HC-06), ensuite il ne faut pas faire de Serial.print() vers le Terminal ou alors il faut faire ça rapidement. Mais franchement, la meilleure solution c'est de paramétrer votre module avec un sketch et pas avec le Terminal!

Software Serial; la mauvaise bonne idée

Si vous travaillez avec un Arduino UNO, vous savez que celui-ci ne comporte qu'une liaison série, sur ses broches 0 et 1. Or ces broches sont utilisées pour recevoir le sketch et pour communiquer avec le Terminal. Malheureusement les modules HC-05 et HC-06 ont besoin d'un port série pour communiquer.
La solution préconisée dans la quasi-totalité des articles, consiste à utiliser la librairie Software Serial. Cette librairie va créer un (ou plusieurs) port série "virtuel" sur votre Arduino. On l'initialise en lui indiquant la connexion (pour notre TX et notre RX virtuel) puis on l'utilise avec print, write etc... Donc:

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11); // Nouveau port série sur PIN 10 et 11
mySerial.begin(9600);
mySerial.println("Coucou");

Le problème c'est que ce tour de force qui consiste à "virtualiser" le port série, est fait par logiciel. Or l'Arduino est une bien petite machine, avec une puissance très limitée. Le résultat c'est que Software Serial n'est utilisable que pour de faibles vitesses. 38400 Bauds est la vitesse maximale que l'on peut compter atteindre (et encore, à cette vitesse j'ai eu parfois des phénomènes un peu étrange). Au delà, donc à 57600 ou à  115200 Bauds, vous allez avoir des pertes de données, avec des octets qui vont arriver avec des valeurs fausses.

Il y a principalement trois options:
  1. Utiliser Software Serial en limitant la vitesse. Si par exemple vous n'avez que quelques données à transmettre, une vitesse même aussi faible que 9600 Bauds peut largement suffire.
  2. Utiliser Software Serial pendant le développement, puis en supprimer l'usage quand tout est prêt. Cette solution est simple mais possède deux inconvénients. En premier il va falloir modifier votre code. Si en théorie c'est facile, l'expérience démontre que le risque de produire des dysfonctionnements est toujours réel. En second il va falloir régler la vitesse de votre module HC pour le développement, puis le régler à nouveau quand ce développement sera terminé. Et si vous trouvez des bugs, il faudra à nouveau changer le code pour réactiver Software Serial et donc rechanger la vitesse du module.
  3. Ne pas utiliser Software Serial. Pour ma part c'est la solution que j'utilise. Cela demande de bien relire son code pour éviter les bugs et de travailler "en aveugle" ou alors si vous disposez de PIN libres, utiliser un petit écran OLED ou LCD pour afficher des petits messages pour vous aider à la mise au point.

Réglage de vitesse

Le réglage de la vitesse des modules se fait avec la commandes "AT+BAUDX" avec X pouvant prendre les valeurs suivantes:
1 -> 1200 Bauds
2 -> 2400 Bauds
3 -> 4800 Bauds
4 -> 9600 Bauds
5 -> 19200 Bauds
6 -> 38400 Bauds
7 -> 57600 Bauds
8 -> 115200 Bauds

Pour passer à 115200 Bauds, on fait donc:

Serial.print("AT+BAUD8");

Changer le code PIN

Le changement du code PIN se fait lui aussi avec une commande AT. Ce code c'est celui que vous taperez sur votre Smartphone pour valider la connexion avec le module.
La commande est "AT+PINXXXX" ou XXXX est une valeur de 4 chiffres. Donc par exemple:

Serial.print("AT+PIN1212");

L'ordre des commandes

Nous savons maintenant comment envoyer des commandes pour changer la vitesse, changer le code PIN et le nom et nous savons que, sur notre HC-06, il faut envoyer chaque commande en une fois pour ne pas avoir d'écart de temps entre les caractères et ensuite attendre 2 secondes (1 en théorie) avant d'envoyer la commande suivante.
Nous allons donc brancher notre HC-06 directement sur RX et TX de l'Arduino car nous voulons travailler à grande vitesse.

Note: sachant que l'Ide Arduino communique avec la plaque via les PIN RX et TX, quand vous envoyer votre sketch, vous devez les déconnecter.

Nous préparons donc notre code et nous l'envoyons:

Serial.begin(9600);
Serial.print("AT+PIN1212");
delay(2000);
Serial.print("AT+BAUD8");
delay(2000);
Serial.print("AT+NAMEMARCEL");
delay(2000);

La compilation se passe bien l'envoi également, tout semble OK. Nous ouvrons notre Smarphone, Android, nous allons dans les préférences Bluetooth et là, surprise, le module est bien visible mais il n'a pas changé de nom. Par contre, quand on veut se connecter sur ce module, on se rend compte que le code PIN a changé et qu'il faut maintenant taper "1212".

Réfléchissons: au départ notre HC-06 est à 9600 Bauds, ce qui est son réglage d'usine. Nous initialisons donc notre port série à 9600 bauds avec Serial.begin(9600);
Ensuite nous envoyons la commande qui change le code PIN. Ayant été paramétré à 9600 Bauds, c'est à cette vitesse que l'Arduino envoi cette commande. Et comme notre HC est aussi réglé à 9600 Bauds, il va comprendre la commande et le code PIN va être modifié.
Ensuite nous envoyons la commande qui change la vitesse. Là encore, nous envoyons cette commande à 9600 Bauds et elle est reçue à 9600 Bauds. Elle est donc comprise par le HC, qui l'exécute.
Sauf qu'à partir de cet instant, notre HC n'est plus réglé à 9600 Bauds, mais à 115200. Or notre Arduino va continuer à travailler à 9600 Bauds et va donc envoyer la commande de changement de nom à une vitesse différente de celle à laquelle le HC attend les données.
La commande ne va donc pas être comprise par le HC et le nom du module ne sera donc pas changé.

Note: pour comprendre ce qui se passe, il vous suffit de débrancher les PIN RX et TX et de faire Serial.print ("Coucou"); Le résultat s'affichera sur le Terminal Serie. Recommencez plusieurs fois en réglant le Terminal Serie à différentes vitesses et vous verrez que la communication n'est bonne que lorsque l'émetteur et le récepteur sont réglés à la même vitesse.

La solution consiste donc à changer l'ordre des commandes:

Serial.begin(9600);
Serial.print("AT+PIN1212");
delay(2000);
Serial.print("AT+NAMEMARCEL");
delay(2000);
Serial.print("AT+BAUD8");
delay(2000);

Ici nous faisons tous les réglages à 9600 Bauds, le changement de vitesse se faisant en dernier.

Une fois, mais pas deux!

Mais attention, ce bout de code ne va fonctionner qu'une seule fois! En effet, les modules HC mémorisent les réglages et conservent ceux-ci même quand on les éteint! En clair, il suffit d'exécuter ce code une seule fois pour que le module conserve son nouveau nom, son nouveau code PI mais, aussi sa nouvelle vitesse!
Si maintenant vous désirez rechanger le nom et que vous vous contentez de modifier la commande AT+NAME puis que vous relancez le sketch, vous verrez que le module ne changera pas de nom. Pourquoi? Parce que ce bout de code communique à 9600 Bauds et que lors de la première exécution de ce code, vous avez changé la vitesse du module en 115200 Bauds.

En clair, il faut faire deux bouts de code: un code pour la première initialisation, qui communiquera à 9600 Bauds (vitesse d'usine des modules) et un code pour les changements qui communiquera à la vitesse réglée lors de la première initialisation. Par exemple:

// Code pour la 1ère init du module, à 9600 Bauds
Serial.begin(9600);   
Serial.print("AT+PIN1212");        // Change code PIN
delay(2000);                    // Attente pour HC-06
Serial.print("AT+NAMEMARCEL");    // Change le nom
delay(2000);
Serial.print("AT+BAUD8");        // Passe en 115200 Bauds

// Code pour les autres changements, à 115200 Bauds
Serial.begin(115200);            // Car maintenant le module est à 115200   
Serial.print("AT+NAMEROGER");    // Change le nom
delay(2000);

Où placer l'initialisation?

Généralement quand on fait du code sur Arduino, on a une fonction setup() et une fonction loop(). Il semble donc logique de mettre l'initialisation dans le setup(). Sauf que non!
Réfléchissons encore un peu: au premier lancement de notre programme, l'initialisation du module va se faire. Mais comme celui-ci mémorise les réglages, au second lancement du programme, notre initialisation ne va plus rien faire du tout. Nous allons donc utiliser de la place en mémoire pour un bout de code complètement inutile.
La solution consiste en fait à créer deux Sketch. Un qui ne vous servira qu'une seule fois par module, afin de faire les réglages de celui-ci: réglage du port série à 9600 bauds (si c'est la première fois que vous réglez le module), changement de nom, changement de code PIN, changement de vitesse. Et c'est fini.
Ensuite dans le code de votre projet, il vous suffit de régler le port série à la bonne vitesse, mais plus la peine d'envoyer de commande AT pour le nom, etc...

Attention: si par la suite vous désirez changer le nom du module ou son code PIN, il ne faudra pas oublier de modifier le réglage de vitesse dans votre sketch de configuration!

Ecriture et lecture de données

Pour écrire des données vers le module HC, il vous suffit de faire des Serial.print() ou des Serial.write().
Veillez cependant à ne pas trop prendre de temps pour envoyer les données, ceci afin d'éviter qu'une transmission du "PATRICK EST CONTENT" ne fasse croire au HC que les "AT" de "PATRICK" sont une commande Hayes!
Pour la réception, c'est simple:

if (Serial.available() > 0)
  { 
    cmd_char = Serial.read();    // Lecture octet reçu
  }

Côté Android

Passons maintenant du côté Android. Là, il y a deux options:
  1. Vous développez un petit truc pour vous amusez, qui tournera sur votre Smartphone "à vous"
  2. Vous développez une application qui sera utilisée par plusieurs personnes que vous ne connaissez pas forcément
Dans le premier cas, il y a peu de choses à tester car vous connaissez votre matériel. Dans le second cas, c'est un peu plus complexe car pas mal de choses doivent être vérifiées:
  • Le smartphone a-t-il un système Bluetooth?
  • Le Bluetooth est-il activé?
  • Le module de l'Arduino est-il apairé c'est-à-dire a-t-il été identifié et son mot de passe  (code PIN) corectement donné
  • Le module de l'Arduino, s'il n'est pas apairé, est-il visible?
  • Enfin il faut vérifier la transmission. Avec, pour ce dernier point, un petit détail génant: alors que la plupart des fonctions de communication Android possédent une gestion de time-out, ce n'est pas le cas pour le Bluetooth. L'accès est donc bloquant et si en cours de transmission, la connexion est interrompu, tout est bloqué. Il faut donc trouver une "ruse" pour éviter cela, ce qui complique encore un peu les choses.
  • etc...

Quelques bouts de code...

Evidement, ne pas oublier de mettre les autorisations Bluetooth dans votre Manifest:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />

et dans votre code, ne pas oublier d'inclure les librairies:
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;

Ensuite voyons toutes les étapes.

Tester si l'appareil a du Bluetooth

 BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
// S'il n'y a pas de réseau Bluetooth sur ce smartphone...
if (btAdapter == null)
{
    // On prévient et on s'en va
}

Si l'appareil a du Bluetooth, on test si celui-ci est activé

if (!btAdapter.isEnabled())
{
    // Le Bluetooth n'est pas activé. On pourra proposer de l'activer de la
    // façon suivante:
   
    Intent enableAdapter = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableAdapter, 0);

Note: pour des raisons de sécurité, on ne peut pas activer Bluetooth automatiquement. Ceci a pour but d'empêcher une application de l'activer et de se connecter sans accord de l'utilisateur.

On recherche maintenant si l'appareil avec lequel nous voulons communiquer, est déjà couplé avec notre Smartphone. Cela se fait en demandant la liste des appareils puis en bouclant sur cette liste pour chercher cet appareil.

 // Demande la liste des appareils couplés
Set bondedDevices = btAdapter.getBondedDevices();
// Si elle est vide c'est qu'aucun appareil n'est couplé
if (bondedDevices.isEmpty())
{
    // Aucun device couplé...
}
else
{
 // On va lire la liste des devices couplés afin de chercher notre Arduino.
    Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();

    List<String> s = new ArrayList<String>();
    // On boucle sur les appareils
    for (BluetoothDevice bt : pairedDevices)
    {
        // On test si le nom de ce module c'est celui du notre
         if (bt.getName().equals("PATRICK"))
        {
         // On peut se connecter
         // A noter qu'on peut tester le Nom mais aussi l'adresse MAC
         // disponible avec if (bt.getAddress().equals(Adresse_Mac))
}

Si on ne trouve pas notre appareil dans la liste des appareils couplés, il faut le chercher dans la liste des non-couplés afin de pouvoir proposer de le coupler. Cela se fait en deux étapes:

D'abord création de l'adapter et du registerReceiver

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
adapter.startDiscovery();

On doit ensuite déclarer notre mReceiver de la façon suivante (le flag_arduino a été déclaré en boolean et initialisé plus tôt dans le code)

 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action))
            {
                //Début de la recherche des device
            }
            else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
            {
                // Fin de la recherche... Si le device n'a pas été trouvé, on l'indique
                if (flag_arduino == false)
                {
                    // petit message et bye bye par exemple
                }
            }
            else if (BluetoothDevice.ACTION_FOUND.equals(action))
            {
                // On a trouvé des devices Bluetooth
                BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(EXTRA_DEVICE);
                String bt_name = device.getName();
                String bt_mac = device.getAddress();
                //Log.i("INFO", "Found device " + device.getName() + " MAC=" + device.getAddress());

                // Nous regardons si le Device est celui que nous voulons
                if (bt_name.equals("PATRICK"))
                {
                    // Un appareil a été trouvé. Il faut aller le coupler
                    flag_arduino = true;   // Flag pour dire qu'on a trouvé
                    // Ici lien vers les préférences Bluetooth.
                    Intent intentOpenBluetoothSettings = new Intent();
       intentOpenBluetoothSettings.setAction(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
        startActivity(intentOpenBluetoothSettings);
                }
            }
        }
    };

Se connecter...

Si on a trouvé notre module et qu'il est apairé, il faut se connecter pour envoyer des données et pour en recevoir. A chaque fois nous allons gérer les exceptions (avec try / catch).

On prépare un socket avec

try
{
// La connection avec createInsecureRfcommSocketToServiceRecord ou avec
// createRfcommSocketToServiceRecord ne marche pas à 100%
// On utilise donc une solution un peu "bizarre" mais qui marche tout le temps...
//socket = bt.createInsecureRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//socket = bt.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
    socket =(BluetoothSocket) bt.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(bt,1);
} catch (Exception e)
{
    // Erreur la connexion a échoué
}

On se connecte ensuite au Socket:

try
{
    socket.connect();
} catch (IOException e)
{
    // Raté, la connexion au seock a échoué
}

Envoyer et recevoir

On déclare l'envoi et la réception si nécessaire

try
{
    outputStream = socket.getOutputStream();
} catch (IOException e)
{
    // Erreur on arrive pas à déclarer la sortie donc l'envoi
}

// Déclare la réception
try
{
    inStream = socket.getInputStream();
} catch (IOException e)
{
    // Erreur on arrive pas à déclarer la réception
}

Et on prépare un flux d'entrée
DataInputStream dinput = new DataInputStream(inStream);

Si on doit envoyer quelque chose:

String code = "Ce qui va être envoyé";
 try
{
    outputStream.write(code.getBytes());
} catch (IOException e)
{
   // Erreur, l'envoi n'a pas été effectué
}

Si on doit recevoir. Là, c'est un peu plus compliqué à cause de l'absence de Time-Out. Nous allons donc créer notre propre Time-Out, avec une simple petite boucle.

// Buffer générale pour les datas
int BUFFER_SIZE = 5000;
byte[] main_buffer = new byte[BUFFER_SIZE];

// Data pour le time out
// Principe: on demande s'il a des bytes disponibles
// Si non, on attend un tout petit peu puis on redemande
// On note le temps global qu'on a attendu sans rien recevoir
// Si ça atteint 8 sec on estime qu'on ne va rien recevoir
// donc bye bye...
int timeOut = 8000; // 8 secondes
int currTime = 0;    // temps global qu'on a attendu
int interval = 50;    // Ecart de temps entre les demandes
stopWorker = 0;     // Flag pour ne pas sortir
main_buffer_idx = 0;
main_buffer_old_idx = 0;

// Tant qu'on a des data, on va les lire...
// Ceci dépend évidement de ce que vous voulez recevoir. Il est par
// exemple intéressant de définir combien de données vous allez recevoir.
// Soit en commençant l'envoi par une valeur donnant le nombre d'octet qui suivent
// soit en prévoyant un caracrère de fin qui sera testé à chaque lecture
// Dans cet exemple, mes données commencent et finissent par "*"
// Donc quand je reçois "*" et que je ne suis pas en train de recevoir
// le 1er caractère, c'est donc que c'est le dernier, ce qu me fait sortir
// de ma boucle.
while (stopWorker == 0)
{
    try
    {
        // Demande combien de byte il y a...
        int bytesAvailable = dinput.available();

        // S'il y en a, on va les lire
        if (bytesAvailable > 0)
        {
            currTime = 0;   // On reçoit donc remet compteur time out à 0

            // Petit buffer de réception pour ce paquet qu'on a reçu
            byte[] packetBytes = new byte[bytesAvailable];
            dinput.read(packetBytes);   // On lit ce paquet

            // On recopie ce paquet dans notre gros buffer
            for (int i = 0; i < bytesAvailable; i++)
            {
                byte b = packetBytes[i];

                // Si ce byte n'est pas le premier et qu'il vaut "*"
                // c'est donc le code de fin. Dans ce cas, on sort
                // de la boucle et on va traiter les données
                if (main_buffer_idx > 0 && b == 42)
                {
                    stopWorker = 1; // Sortie par fin de data
                }
                // On recopie ce byte reçu dans notre gros buffer principal
                // Il aura donc aussi bien le * de début que celui de fin
                main_buffer[main_buffer_idx] = b;
                main_buffer_idx++;  // Sinon passe au suivant
            }
        }
        // Il n'y a pas d'octets à arriver. On va donc regarder
        // si on a atteint le Time out
        else if (currTime < timeOut)
        {
            // Si le time out est pas atteint, on attend un peu
            // La durée est dans interval qui ici vaut 50 millisecondes
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                // ...
                // exception handling code
            }
            // Et on compte ce temps...
            currTime += interval;   // On augmente le compteur de timer out
        }
        else
        {
           
            // Timeout. On réinit, on attend encore, on alerte etc...
            // C'est comme vous voulez...
           
        }

        ;
    } catch (IOException e)
    {
        // Si la lecture rate, on s'en va
       
    }
}

Et enfin, ne pas oublier de fermer la connexion!

try
{
    socket.close();
} catch (IOException e)
{
    // Echec de la fermeture de la connexion
}

Ouf! Rien de bien compliqué pour la partie Android, mais un multitude de petits cas avec plein de trucs qui peuvent dépendre de l'utilisateur.

Conclusion

La gestion du Bluetooth entre un Arduino et un appareil Android est l'exemple typique de la partie programmation "pénible": rien de compliqué mais plein de petits détails, de choses à faire dans un certain ordre, qui marche une fois mais pas deux etc... J'espère qu'avec cet article vous arriverez à vous débrouiller. En tout cas, en suivant ces conseils vous devriez obtenir une communication fiable à 115200 Bauds.

Aucun commentaire:

Enregistrer un commentaire

Arduino - C+ASM

Dans un précédent article disponible ici nous avons vu comment faire pour utiliser TextWrangler (ou BBEdit) en lieu et place de l'édite...