Comment chercher une sous-chaîne spécifique ?

Bonjour à tous,

Comment puis-je vérifier la présence d'une sous-chaîne spécifique dans une chaîne ?
J'ai bien trouvé la méthode substring(index,index2) mais dans mon cas je ne sais pas à quel endroit de la chaîne se trouve ma sous-chaîne...

J'ai bien une idée :

-Parcourir la chaîne.
-Comparer le caractère(c-ch) à l'indice courant(x-ch) de la chaîne au caractère(c-s-ch) à l'indice (x-s-ch) de la sous-chaîne recherchée.
-S'il est identique on compare c-ch de x+1 à c-s-ch de x-s-ch+1.
-S'il est différent on compare c-ch de x+2 à c-s-ch de x-s-ch.
-S'il est identique on compare c-ch de x+2 à c-s-ch de x-s-ch+1.
-S'il est différent ...
-S'il est identique ...
-S'il est différent ...
-S'il est identique et que c'est la fin de la sous chaîne on retourne "sous-chaîne trouvée".
-S'il est différent et que c'est la fin de la chaîne on retourne "sous-chaîne non trouvée".

Quelqu'un peut m'expliquer comment faire ça en Arduino et me dire si c'est une bonne méthode ? :smiley:

Elle est surement mal rédigée mais je pense avoir l'idée...

En gros tant que les caractères sont identiques on avance sur la chaîne et sur la sous-chaîne et dès qu'ils sont différents on avance sur la chaîne mais on retourne au début de la sous-chaîne.

Ah c'est parfait !
Merci !

Merci pour ce code bien détaillé !

De mon coté j’ai essayé de faire cela :

char Stringbuffer[inputStringGSM.length()+1];
String souschaine = "AT+CREG=1\r\r\nOK";
const int taille = souschaine.length()+1;
char SubstringBuffer[taille];
char *ret;
int cas;

void setup() {//[...]}

void loop() {
    //[...]
    switch(cas){
    case 0 :
      //retourne inputStringGSM (type String).
      readFromGsm();

      //Pour utiliser strstr il faut donc caster 'inputStringGSM'.
      inputStringGSM.toCharArray(Stringbuffer, inputStringGSM.length()+1);
      //Il faut également caster 'souschaine', bien que j'aurais pu la déclarer directement en char array. 
      souschaine.toCharArray(SubstringBufferss, souschaine.length()+1);
      
      // La sous-chaîne est-elle dans la chaîne ? 
      ret = strstr(Stringbuffer, SubstringBuffer);
      if (ret != NULL){
        Serial.println("Pas dans la chaîne");
        cas = 1;
        break;
      }else {
        Serial.println("dans la chaîne");
        cas = 2;
        break;
      }
    case 1: 
    //[...]
    }
    //[...]
}

Mais j’ai ce message d’erreur : array bound is not an integer constant before ‘]’ token.
Relatif à cette ligne : char Stringbuffer[inputStringGSM.length()+1];

Pourtant j’ai vu plein d’exemple avec cette méthode…
En attendant je vais essayer de comprendre ton code, pas encore tout pigé x)

N'utilisez pas la classe String... restez au niveau des tableaux de caracteres

D'accord,
Mais ça n'explique pas l'erreur !

Bonjour,

Ce serait mieux si tu nous donnais tous les éléments avec le programme complet, mais je suppose que inputStringGSM.length() n'est pas une constante
d'ou l'erreur dans char Stringbuffer[inputStringGSM.length()+1];

Si si :wink:

Et si vous utilisez des instances de la classe String dans ce cas utilisez les méthodes associées d'extraction (indexOf) et comparaison (==), ne fabriquez pas de nouveaux buffers

Mais cette classe n'est pas idéale pour des microcontroleurs avec peu de ram, le mieux donc étant de faire les choses plus près du hardware avec les fonctions C

La taille de inputStringGSM varie, oui. Mais même si c'était un tableau de caractères elle varierait.

Elle varie en fonction de la réponse que me retourne le GSM donc oui elle varie tout le temps...

A quoi sert strstr() si on ne peut pas chercher une sous-chaîne dans une chaîne qui varie.

Comment j'utilise strstr si ma chaîne varie ?

Je sais bien que String n'est pas adapté aux microcontrôleurs mais c'est étrange cette facilité que j'ai à l'utiliser... Tout le monde rabâche qu'il faut utiliser des tableaux de caractères mais personnellement j'ai toujours pas réussi à lire un seul caractère sur mon bus série et à l'afficher. J'ai même pas trouvé un exemple sur internet !! Il y en a pas un pareil ! Alors qu'avec String ça marche du premier coup...

Si quelqu'un à assez de courage pour m'expliquer comment changer mes habitudes de String pour passer à du 100% tableau de char je suis preneur.

Il faut un buffer, ok. Comment je fais un buffer dynamique ? comme ça : char mybuffer[] =""; ?
Il faut un index, ok. J'imagine que ça c'est bon : int index =0; ?
Il faut un endroit pour stocker le caractère courant, ok. Comme ceci : char c; ?

Bon, tant qu'il y a des caractère on read ?
Du coup : While(Serial.available > 0){c=Serial.read();} ?

Je pense que j'ai compris tout ça mais pourquoi je n'arrive toujours pas à lire un caractère.

char mybuffer[]="";
int i = 0;
char c;

void setup() {
Serial.begin(9600);

while(Serial.available() > 0){
    c = Serial.read();
    mybuffer[i] = c;
    i++;
  }
Serial.println(mybuffer);

}
void loop() {
}

Pouvez vous me faire un exemple simple (très simple) pour accompagner cette douloureuse transition de String à tableau de char que je m’apprête à faire ? :sob:

Tout le monde rabâche qu'il faut utiliser des tableaux de caractères mais personnellement j'ai toujours pas réussi à lire un seul caractère sur mon bus série et à l'afficher. J'ai même pas trouvé un exemple sur internet !! Il y en a pas un pareil ! Alors qu'avec String ça marche du premier coup...

lire Serial Input Basics (Example 2 - Receive with an end-marker ) et connaître les fonctions des librairies standards stdlib.h et
string.h

Comment je fais un buffer dynamique ?

En utilisant malloc() et free() par exemple —> mais ne le faites pas.

Vous en prenez un assez grand pour stocker la réponse la plus longue que vous attendez jusqu'à la réception d'un marqueur de fin de réponse (souvent un retour à la ligne) le plus souvent ce qui vous intéresse à une longueur connue - ou alors vous pouvez traiter par petits bouts la réponse quand elle arrive avec un parser.

Utiliser de la mémoire dynamique est ce qui morcelle votre mémoire et qui - si c'est mal géré - au final transforme le tas en gruyère et vous ne pourrez plus allouer un bloc contigu de mémoire à un moment et votre programme va crasher sans que vous puissiez identifier pourquoi. (cf par exemple ce post)

Merci J-M-L.

Voici donc ma nouvelle façon de faire :

#define SIM800_RESET_PIN 77
#define PWKEY 6
const byte nbChars = 64;
char receivedChars[nbChars];
boolean newData = false;

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);

  pinMode(SIM800_RESET_PIN, OUTPUT);
  pinMode(PWKEY, OUTPUT);
  digitalWrite(PWKEY, HIGH);
  delay(1000);
  digitalWrite(SIM800_RESET_PIN, LOW);
  delay(100);
  digitalWrite(SIM800_RESET_PIN, HIGH);
  delay(2000);
}

void loop() {
   
      delay(2000);
      
      // Network registration
      commandStringGSM = "AT+CREG=1";
      Serial1.println(commandStringGSM);
      delay(50);
      readFromGsm();     
      showNewData();
      delay(500);
      
      // Set text mode
      commandStringGSM = "AT+CMGF=1";
      Serial1.println(commandStringGSM);
      delay(50);
      readFromGsm();
      showNewData();
      delay(500);

}

ma fonction ReadFromGsm :

void readFromGsm(){

    static byte ndx = 0;
    char endMarker = '\n';
    char rc;
    
    while(Serial1.available() > 0 && newData == false){
    
        rc = Serial1.read();
        if (rc != endMarker){
          receivedChars[ndx] = rc;
          ndx++;
          if (ndx >= nbChars) {
            ndx = nbChars -1;
          }
        }else {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
        }
    }
}

et ma fonction showNewData :

void showNewData(){
    if (newData == true) {
        Serial.print("Received stuff : ");
        Serial.println(receivedChars);
        newData = false;
    }
}

J'ai bien les réponses du GSM mais c'est pas encore la grande forme quand on regarde le moniteur série :

Received stuff : AT+CMGF=1    //ici c'est bien la réponse attendue...


Received stuff : OK                 //...suivie de "OK" c'est parfait pour vérifier la réponse ([b]1[/b])

Received stuff : 

Received stuff : +CPIN: READY

Received stuff : 

Received stuff : SMS ReaAT+CREG=1


Received stuff : OK

Received stuff : 
Received stuff : ERROR
AT+CMGF=1


Received stuff : Call ReAT+CMGF=   //ici ça va pas ! On a "Call re" ([b]2[/b]) puis "AT+CMGF="
Received stuff : OK                        //pas d'\r\r\n avant le OK ça va pas !

La réponse attendue est : "AT+CREG=1\r\r\nOK" et la suite importe peu.

(1) J'aurais voulu vérifier la réponse en la comparant à la réponse attendue. Simplement :
if(receivedChars == "AT+CREG=1\r\r\nOK"){//bonne réponse}else{//mauvaise réponse}

(2) "Call re" est en fait l'URC "Call ready" qui a été mangé je ne sais comment, et il manque le 1 à "AT+CMGF=" ! et il n'y a pas d'\r\r\n avant le "OK" comme au niveau du (1).

Il y a d'autres URC qui viennent parasiter la lecture comme :
"SMS ready" ou "+CPIN: READY" ou encore "RDY" et j'en passe...

Cette idée de lire tant qu'on est pas au caractère de fin de chaîne prédéfinit n'est peut-être pas adaptée pour la vérification que je souhaite effectuer.

Il faut impérativement que je reçoive "AT+CREG=1\r\r\nOK" pour vérifier que la commande à bien été appliquée. Après il est possible de vérifier "AT+CREG=1\r\r\n" dans un premier temps puis lors d'une seconde lecture vérifier "OK" mais le cas (2) montre bien que c'est pas gagné...

Comment pourrait-je vérifier la réponse de mon GSM ? :confused:

je ne sais pas ce qu'est commandStringGSM c'est défini où?

Vous avez encore un petit problème de compréhension sur le port série

il faudrait que la fonction readFromGsm soit appelée tant que vous n'avez pas reçu le marqueur de fin de ligne / réponse surtout en ayant une communication à 9600 bauds

en effet votre while va vider le buffer du port série plus vide que les données n'arrivent et donc à un moment while(Serial1.available() > 0 sera faux, aucune donnée ne sera dispo, mais ce n'est pas parce que vous avez tout reçu, c'est parce qu'il faut attendre un peu pour recevoir la fin et donc vous recevez une réponse tronquée (et la prochaine fois que vous appelez la fonction vous allez recevoir ce qu'il vous manque ce qui peut vous induire en erreur, par exemple lire à ce moment là le OK qui devait être reçu avant)

le fonctionnement (si vous regardez le tuto) c'est d'appeler dans la boucle leur fonction recvWithEndMarker() et tant que newData n'est pas vrai, il y a encore des choses qui doivent arriver.

Bonjour,

commandStringGSM est de type String et est définie avant le void setup() comme suit :
String commandStringGSM = "";

Je ne l'ai pas encore changé en tableau de caractères mais est-ce la cause de mon problème ?
Je m'en sert juste pour écrire sur le bus série de mon module GSM, pour lui envoyer les commandes.

Je dois définir '\0' comme étant mon caractère de fin de chaîne (endMarker) ?

Je dois appeler readFromGsm tant que je n'ai pas reçu le marqueur de fin de ligne ? Dans l'exemple du tuto il appelle simplement recvWithEndMarker() dans le void loop().

Je dois mettre un délai à quel endroit ? Il n'y a pas de délai dans le tuto, et la communication est à 9600 bauds, comme pour moi.

Si on prend cette ligne du moniteur série :
Received stuff : SMS ReaAT+CREG=1

"Rea" est en fait "Ready", tronqué, mais je n'ai jamais reçu "dy" par la suite.

J'appelle ma fonction dans la boucle et tant que newData n'est pas vrai, comme dans le tuto, je ne comprend pas votre dernière phrase.

Merci encore, je teste tout ça à 9h.

Bonjour,

commandStringGSM est de type String et est définie avant le void setup() comme suit : ...

On dirait que le code que vous postez n'est pas celui que vous utilisez... (et si elle est déclarée dans le setup elle ne peut pas être utilisée ailleurs...)

Difficile de vous aider si on ne voit pas votre vrai code

  • pas besoin de delay dans la gestion du port série si vous attendez tranquillement la réception du caractère de fin le ligne / message

Niconnexion:
commandStringGSM est de type String et est définie avant le void setup() comme suit :
String commandStringGSM = "";

C'est une variable GLOBALE utilisable partout !

Le caractère de fin de ligne / message ?
Vous voulez dire \n ?

Si je m'arrête de lire à la fin de la ligne je n'aurais que "AT+CREG=1\r\r\n".
Or j'ai besoin de vérifier que j'ai reçu "AT+CREG=1\r\r\nOK".

Je suis donc obligé de faire deux lectures ?
Deux "tant que je n'ai pas reçu le marqueur de fin de ligne" ?

oops - j’avais lu “dans le setup()” pas “avant le setup” désolé besoin de mes lunettes :slight_smile:

En fait le marqueur de fin que vous attendez dans votre cas comporte plusieurs caractères “OK\r\n” sans doute.

Voici un extrait d’un de mes codes que j’avais hacké rapidement un jour pour obtenir l’heure depuis un serveur NTP (cf post #11 pour le contexte et les définitions)

Il contient 2 fonctions espATCommand() qui envoie une commande à mon ESP et la fonction waitForString() qui attend une réponse identifiée par une chaîne et qui utilise (habillement) un buffer circulaire de la bonne taille pour comparer les derniers caractères reçus avec ce que l’on attend, au sein d’un timeout

// --------------------------------------
// waitForString wait max for duration ms
// while checking if endMarker string is received
// on the ESP Serial port
// returns a boolean stating if the marker
// was found
// --------------------------------------

boolean waitForString(const char * endMarker, unsigned long duration)
{
  int localBufferSize = strlen(endMarker); // we won't need an \0 at the end
  char localBuffer[localBufferSize];
  int index = 0;
  boolean endMarkerFound = false;
  unsigned long currentTime;

  memset(localBuffer, '\0', localBufferSize); // clear buffer

  currentTime = millis();
  while (millis() - currentTime <= duration) {
    if (ESPSEPRIAL.available() > 0) {
      if (index == localBufferSize) index = 0;
      localBuffer[index] = (uint8_t) ESPSEPRIAL.read();
      endMarkerFound = true;
      for (int i = 0; i < localBufferSize; i++) {
        if (localBuffer[(index + 1 + i) % localBufferSize] != endMarker[i]) {
          endMarkerFound = false;
          break;
        }
      }
      index++;
    }
    if (endMarkerFound) break;
  }
  return endMarkerFound;
}


// --------------------------------------
// espATCommand executes an AT commmand
// checks if endMarker string is received
// on the ESP Serial port for max duration ms
// returns a boolean stating if the marker
// was found
// --------------------------------------

boolean espATCommand(const char * command, const char * endMarker, unsigned long duration)
{
  ESPSEPRIAL.println(command);
  return waitForString(endMarker, duration);
}

ça devrait correspondre à ce que vous essayez de faire

Un grand merci J-M-L, je peux maintenant faire ma vérification ! :smiley:

Voici mon code, adapté du votre :

#define SIM800_RESET_PIN 77
#define PWKEY 6
const char * NETWORK_REGISTRATION_STR = "AT+CREG=1\r\r\nOK\r\n";
const char * TEXT_MODE_STR = "AT+CMGF=1\r\r\nOK\r\n";
const char * INIT_HTTP_STR = "AT+HTTPINIT\r\r\nOK\r\n";
const char * HTTP_SESSION_STR ="AT+HTTPPARA=\"CID\",1\r\r\nOK\r\n";

// --------------------------------------
// waitForString wait max for duration ms
// while checking if endMarker string is received
// on the GSM Serial port
// returns a boolean stating if the marker
// was found
// --------------------------------------

boolean waitForString(const char * endMarker, unsigned long duration)
{

  int localBufferSize = strlen(endMarker); // we won't need an \0 at the end
  char localBuffer[localBufferSize];
  int index = 0;
  boolean endMarkerFound = false;
  unsigned long currentTime;

  memset(localBuffer, '\0', localBufferSize); // clear buffer

  currentTime = millis();
  while (millis() - currentTime <= duration) {
    if (Serial1.available() > 0) {
      if (index == localBufferSize) index = 0;
      localBuffer[index] = (uint8_t) Serial1.read();
      Serial.print(localBuffer[index]);
      endMarkerFound = true;
      for (int i = 0; i < localBufferSize; i++) {
        if (localBuffer[(index + 1 + i) % localBufferSize] != endMarker[i]) {
          endMarkerFound = false;
          break;
        }
      }
      index++;
    }
    if (endMarkerFound) break;
  }
  if (endMarkerFound == 1){
    Serial.println("Good answer received !");
  }else {
    Serial.println("Bad answer...");
  }
  return endMarkerFound;
}


// --------------------------------------
// gsmATCommand executes an AT commmand
// checks if endMarker string is received
// on the GSM Serial port for max duration ms
// returns a boolean stating if the marker
// was found
// --------------------------------------

boolean gsmATCommand(const char * command, const char * endMarker, unsigned long duration)
{
  Serial1.println(command);
  return waitForString(endMarker, duration);
}

void setup(){

Serial.begin(9600); while (!Serial);
Serial1.begin(9600); while (!Serial1);

// Pin definitionpinMode(SIM800_RESET_PIN, OUTPUT);
pinMode(PWKEY, OUTPUT);
digitalWrite(PWKEY, HIGH);
delay(1000);
digitalWrite(SIM800_RESET_PIN, LOW);
delay(100);
digitalWrite(SIM800_RESET_PIN, HIGH);
delay(2000);

Serial.println("Sending AT+CREG=1...");
// Network registration
gsmATCommand("AT+CREG=1", NETWORK_REGISTRATION_STR, 1000);
delay(1000);
Serial.println("Sending AT+CMGF=1...");
// Set GSM to text mode
gsmATCommand("AT+CMGF=1",TEXT_MODE_STR, 1000);
delay(1000);
Serial.println("Sending AT+HTTPINIT...");
// Initialize HTTP
gsmATCommand("AT+HTTPINIT",INIT_HTTP_STR, 1000);
delay(1000);
Serial.println("Sending AT+HTTPPARA=\"CID\",1...");
// Set HTTP session
gsmATCommand("AT+HTTPPARA=\"CID\",1",HTTP_SESSION_STR, 1000);

}
void loop(){
}

Je me suis demandé s’il n’était pas tout autant bien de vérifier la réponse complète, ça mange pas de pain ! Même si attendre “OK\r\n” devrait être suffisant en théorie. Dites moi si l’ensemble vous parait valide.

C’est fou, je n’y croyais plus !

Voila de quoi illustrer un peu :

Sending AT+CREG=1...

 AT+CREG=1
OK
Good answer received !

Sending AT+CMGF=1...

+CPIN: READY

+CREG: 2
AT+CMGF=1


OK
Good answer received !

Sending AT+HTTPINIT...


AT+HTTPINIT

OK

Good answer received !


Sending AT+HTTPPARA="CID",1...


Call Ready

AT+HTTPPARA="CID",1


OK

Good answer received !

On remarque bien que les URC ne faussent plus la vérification :smiley:

Comme vous pouvez le voir j’essaye quelques commandes pour la partie HTTP.
J’aurais quelques conseils à vous demander pour débuter ce pan de mon projet.
J’essaye de trouver un moyen de communiquer avec mon module GSM via une interface web pour pouvoir contrôler toute ma maquette à distance, au delà des quelques commandes que vous avez pu me voir essayer j’ignore comment m’y prendre…

Faut-il que je fasse un nouveau topic pour traiter cette question ?

Merci encore !!

J'ai pris soin d'utiliser des booléen.. donc ça
  if (endMarkerFound == 1){c'est mieux si c'est écrit comme

  if (endMarkerFound){

ou

  if (endMarkerFound == true){

Je me suis demandé s'il n'était pas tout autant bien de vérifier la réponse complète, ça mange pas de pain !

je m'arrête dans la lecture du buffer du port série effectivement dès que je trouve la réponse attendue, il se peut donc qu'il reste des trucs dans le Serial buffer si votre chaîne de test n'est pas la fin de la réponse. ce n'est pas trop grave, elle sera lue (ou écrasée) par la prochaine réponse, l'important étant de savoir si la commande avait bien été exécutée.

Comme ma fonctionne retourne un booléen, l'idéal serait quand même de lire si la commande s'est bien passée... sinon même pas besoin d'attendre la réponse :slight_smile: --> donc ce serait bien de faire par exemple

Serial.println("Sending AT+CREG=1...");
// Network registration
if (gsmATCommand("AT+CREG=1", NETWORK_REGISTRATION_STR, 1000)) {
    // c'est bon on peut continuer
} else {
    // erreur à gérer
}

Faut-il que je fasse un nouveau topic pour traiter cette question ?

Oui une fois que vous avez un code stable de ce côté là, comme c'est vraiment différent autant avoir un autre post avec un sujet adapté, vous aurez plus de contributeurs

Je prend note des quelques modifications que vous proposez, c’est effectivement mieux comme ça ! J’ouvrirais un autre post concernant le HTTP dans l’après midi.