Manipulation des String avec Arduino

Introduction
Passer une chaîne de caractère à une fonction ne coule pas forcement de source pour les débutants.
Cela peut sembler simple mais une petite erreur d'interprétation peut transformer l'écrire votre programme en un véritable parcours du combattant!

Voici donc quelques précisions non seulement utiles mais parfois vraiment importante.

Avis au lecteurs expérimentés:
Les puristes ne manquerons pas de remarquer que j'ai pris quelques libertés/raccourcis, parfois discutable. N'hésitez à envoyez un petit mail (ou commentaire) avec vos notes :-) 

Définition de "chaîne de caractère"
Une chaîne de caractère (dite "string" terme anglais) peut être représenté de deux façons:
  1. Vous pouvez utiliser le type de donnée String
  2. Vous pouvez constituer une chaîne de caractère depuis un tableau de caractère ("array of char") contenant les caractères de la chaîne et terminé avec le caractère NULL (code ASCII 0, aussi encodable sous la forme '\0'). 
La classe String, vous permet d'utiliser et manipuler une chaîne de caractère (du texte) de façons plus complexes qu'avec un tableau de caractère (arduino.cc). String permet de concaténer des chaînes de caractères, les ajouter, faire une recherche/remplacer une sous chaine (substrings), et plus encore.
La classe String utilise plus de mémoire qu'un simple tableau de caractères mais est aussi plus utile.
Vous trouverez plus d'informations sur la classe String sur Arduino.cc


Ce qu'il faut absolument savoir
Il faut avoir les idées au clair avec les points suivants:

Constante String
Il est possible de déclarer une constante string (incluant donc le NULL) à l'aide de:
"Voici une constante string"

Notez la présence des doubles quotes (")

Constante caractère
Il est aussi possible de déclarer une constante caractère (char, un caractère) à l'aide de:
'x'

Notez ici la présence des simples quotes (')... cette distinction avec les constantes string est capitale! Un caractère c'est avant tout une information stockée sous la forme d'une valeur numérique. Alors qu'une String, c'est une chaîne de caractère (du texte).
Si vous vous arrachez les cheveux sur votre code... avec les string, char, array, la raison tiens peut-être dans l'utilisation de ' au lieu de " .

Ne pas confondre constante string et constante caractère
Les deux définitions suivante sont semblables MAIS PAS IDENTIQUES!
Le compilateur, lui, fera une énorme différence entre les deux!
"Voici une chaine de caractère"

'Et_Ceci_Un_Tableau_De_Caractère'

La première définition est une constante String (un chaîne de caractère sous la forme de texte, donc terminée avec NULL).
La deuxième définition est un énumération de caractère (de type char)... donc plutôt vu comme un tableau de valeurs numériques entre 0 et 255.  Pas de caractère NULL à la fin! Il se fait que chaque caractère a une valeur numérique (le fameux code ASCII), c'est pour cela qu'il est possible de remplir un tableau de caractère (array of char) avec quelque-chose qui s'apparente très fortement à une chaîne de caractère. Rien n'aurait empêché de le remplir avec une série de valeur numérique.

Si vous voulez une vraie chaîne de caractère (donc une String) il faut absolument utilise des doubles guillemets

String et tableau de caractère
Pour fixer les idées (et les nuances)...
Une chaîne de caractère constante String est avant tout une série de caractère char. Pour aider le concept, pensez à votre string comme à un tableau de caractère si cela vous semble plus évident.
Il peut être directement stocké dans un tableau de caractère. 
Ce "tableau" doit contenir un élément supplémentaire réserver au caractère NULL (\0) servant à identifier la fin de chaîne de caractère.
Voici quelques définitions correctes... sous forme de tableau.
Notez que le tableau est toujours plus grand d'un caractère.

char Str2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'};
char message[6] = "hello";

Il est aussi possible d'utiliser un objet String(), qui peut être réduit à un tableau de caractère (conceptuellement, pour faciliter la compréhension, et contenant également le caractère NULL en fin de chaîne).
Mais gardez à l'esprit qu'un objet String() et une constante String sont des entités différentes! Si le compilateur sait transformer une constante string en tableau de caractère ou en objet String(), l'exploit s'arrêtera là! Vous devrez vous occuper des autres transformations vous même (et surtout ne pas faire de soupe entre les tableau de caractère et les objet String() )
Bon, cela parait peut être un peu compliqué mais l'idée maîtresse est là.

Voici comment définir des objet String():
 
// Utiliser une constante String
String stringOne = "Hello String";
// Convertir un caractère unique (char) en String
String stringOne = String('a');
// Convertir une constante String en objet String
String stringTwo = String("This is a string");
// Concaténation de deux chaines de caractères
String stringOne = String(stringTwo + " with more");


Fonction et chaine de caractère en paramètre
Pour finir, voici comment appeler une fonction avec une chaîne de caractère en paramètre.
Cette version se base sur une chaîne de caractère stocké sous forme de tableau de caractère (en NULL à la fin).
Nous verrons plus tard comment le faire avec un objet String()

sendError( client, HTTP_ERR_MethodNotAllowed, "Method Not Allowed" );

Notez l'usage du double guillemet dans le dernier paramètre, cela permet de passer un paramètre de identifier comme une constante String.
La constante string peut être convertie en char* ou en String() par le compilateur.

Si l'on se penche sur la fonction sendError...
void sendError( EthernetClient client, int HtmlErrorCode, char* sInfo ){
    char c;
    
    // Jeter le reste de la Requete HTTP à la poubelle
    while( client.available() ){
      c = client.read();
    }
    // Donner du temps au browser pour recevoir la réponse
    delay( 1 );
    
    // Envoyer un HEADER http 
    client.println("HTTP/1.1 ");
    client.print( HtmlErrorCode );
    client.println(" ERROR");
    client.println("Content-Type: text/html");
    client.println("Connection: close");
    client.println();   
    // Envoyer le contenu HTML 
    client.println("<!DOCTYPE HTML>");
    client.println("<html>");
    client.print("HTML ERROR CODE (");
    client.print( HtmlErrorCode );
    client.println(")<br />");
    client.print("Info: ");
    client.println( sInfo );
    client.println("</html>");
}

Notez le dernier paramètre sInfo déclaré comme char*, un pointeur vers un caractère (le premier d'une chaine de caractère se terminant obligatoirement par un NULL).

Fonction et type String en paramètre
Une autre implémentation possible est la suivante; utilisant un objet String.
Notez qu'il est difficile de faire cohabiter les types String et char[] (array of char) et qu'il n'est pas possible de faire passer l'un pour l'autre.
Difficile d'appeler une fonction nécessitant un String() avec un tableau de caractère (et vice versa).

Il est toujours possible de convertir une string() en tableau de caractère en utilisant la fonction string.toCharArray(buff, len) mais cela implique la définition (et allocation) d'un tableau pour y transférer l'information de la String.

void sendError( EthernetClient client, int HtmlErrorCode, String sInfo){ 
    ...
    // Envoyer un HEADER http 
    client.println("HTTP/1.1 ");
    client.print( HtmlErrorCode );
    client.println(" ERROR");
    ...
   client.print("Info: ");
    client.println( sInfo );
    ...
}

Ce qui permet d'appeler la fonction comme suit:

String errMsg =  "Message d erreur!!";
sendError( client, HTTP_ERR_ServerError, errMsg );

Ou encore

String errMsg =  String( "Message d erreur 2");
sendError( client, HTTP_ERR_ServerError, errMsg );

Et finalement avec

sendError( client, HTTP_ERR_ServerError, "Message d erreur 3" );

Ressources

1 commentaire:

  1. Bonjour,
    je suis tombé par hasard sur cet article de 2014 et il est très bien, à 2 exceptions près:
    * orthographe (mais ça n'a pas du traumatiser grand monde)

    * tous les liens sont cassés. Je vais tenter de remplacer un des liens par ...3.
    c'est ce dernier point qui me chagrine:
    les precautions d'emploi (et pourquoi un accés direct à un tableau de chars (avec test de l'indice...) strncat, snprintf -j'aime cette fonction, très flexible) sont préférables à une implementation de la bibliothèque std C++ qui peut provoquer des fuites mémoire ou de la fragmentation sur un processeur sans beaucoup de RAM ) ne sont pas accessibles:
    un bon fil de discussion est (mais très vieux) https://forum.arduino.cc/t/the-hatred-for-string-objects-to-string-or-not-to-string/121801/11
    Il est confirmé et détaillé dans https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/
    et on a une bonne explication, en français ce coup ci, dans
    https://forum.arduino.cc/t/ecouter-le-port-serie-ou-un-keypad-applicable-a-1-flux-de-donnees-asynchrone/480990


    (la consommation de String est de 1400 octets dans son cas, soit 3-5% de la mémoire flash -qui est rare sur un Uno; si , de plus, la RAM fuit (cas où on fait StringRecu= StringRecu+String(cRecu);)

    RépondreSupprimer