Traiter des commandes reçue par le port Série [résolu]

Bonsoir, cela fait des heures que j'esssaye simplement de récupérer et exécuter des commandes reçues par le port série, voilà mon code :

boolean ledState[13] = {false};

void setup () {
  
   pinMode (13, OUTPUT);
   Serial.begin (9600); 
}

void loop () {
  char command[50] = "";
  int i = 0;
  
  while (Serial.available ()) {
    char in = Serial.read ();
    command[i] = in;
   
    if (in == '\n')
      exeCmd (command);
      
    Serial.println(command);
    
    ++i;
  }
  
}

void exeCmd (char * cmd) {
  
  
  if (strcmp(strtok(cmd, " "), "Set") == 0) {
    // Action sur les sorties.
  }
  
  else if (strcmp(strtok(cmd, " "), "Check") == 0) {
    // Vérifie l'état d'une sortie
    int num = atoi (strtok(NULL, " "));
    if (num > 0 && num < 13)
      Serial.println (ledState[num]);
    else
      Serial.println ("Erreur cette sortie n'existe pas");
  }
  
  else if (strcmp(strtok(cmd, " "), "Read") == 0) {
    // Lie la valeur d'une entrée
    int num = atoi (strtok(NULL, " "));
    if (num > 0 && num < 5)
      Serial.println (analogRead(num));
    else
      Serial.println ("Erreur cet entrée n'existe pas");
  }
  
  else
    Serial.println ("Commande inconnue");
  
  
}

Le but est d'envoyer une commande du style "Read 3" ou "Check 12" ou "Set 3 1" (pas encore codé le set") en série, mais ça ne fonctionne jamais, là j'ai mis le print (command); pour débuguer et celui ci ne renvoie rien, comme si le code ne lisait rien des commandes que j'envoie.

Si vous pouviez me dire si vous voyez une erreur, merci d'avance.

Salut,

Je suppose que tu envoies tes commandes séries par le terminal de l'IDE? tu es sûr que tu travailles des deux côtés à la même vitesse? si oui, il n'y a pas de raison...

Pourquoi ne pas commencer simplement avec des caractères visibles? le '\n' n'est pas facilement débuggable.

Je te propose de remplacer '\n' par ';' , ça se fait beaucoup dans les commandes de ce type. la touche entrée ne renvoie pas toujours la même séquence selon le terminal utilisé, et un retour charriot complet comporte deux caractères. Tu peut prendre exemple sur le format HPGL dont les commandes sont du genre "XX123,456,75.2,9.95,0,1;", où XX sera toujours deux lettres majuscules (commande), suivi d'arguments séparés par une virgule (ici, 6 arguments : 123 , 456 , 75.2 , 9.95 , 0 et 1), et un caractère de terminaison (le fameux ';').

Autre possibilité, le protocole que j'utilise tout le temps, qui oublie les interminables chaînes de caractère, mais impossible à tester avec l'IDE car j'envoie des valeurs de 0 à 255. Cette méthode impose de définir un nombre fixe d'argument pour chaque commande, et ne protège pas d'une éventuelle éventuelle perte d'un octet :

void loop() {
  byte n0, n1, n2, n3, n4;

  while(!Serial.available());  // attend le prochain octet
  n0 = Serial.read();
  switch(n0){
    case 48:          // comande '0', pas d'argument : réinitialisation
      setup();
      break;
    case 49:         // Commande '1', 2 arguments
      while(!Serial.available());  // attend le prochain octet
      n1 = Serial.read();
      while(!Serial.available());  // attend le prochain octet
      n2 = Serial.read();
      action1(n1, n2);
      break;
    case 50:                  // led 13 on/off, 1 argument
      while(!Serial.available());  // attend le prochain octet
      n1 = Serial.read();
      if (n1 == 0) {         // 0 : extinction led
        digitalWrite(13, LOW);
      } else {
        digitalWrite(13, HIGH);
      break;
  }
}

Pour revenir à ton code, tu devrais essayer de déclarer ton " char command[50] = ""; " en global et non en local (c'est à dire en dehors de loop())... puis de mettre ton Serial.println(command); juste après ton command = in;
Pense à réinitialiser i dès la fin d'une commande pour revenir au début de ton tableau, car tu risques de déborder...

C'est la structure de la boucle qui pose problème.

Lorsque tu saisis à la main des commandes au clavier il se passe beaucoup de temps (à l'échelle du micro) entre l'entrée de 2 caractères consécutifs, donc tu ne restes pas dans ton while (Serial.available ()) et du coup tu réinitialises ta variable d'index. Donc tu n'as jamais une ligne complète dans ton buffer de réception.
La variable d'index ne devrait être remise à 0 que dans 2 cas: soit un hors temps important, soit suite à l'identification d'un terminateur de ligne (\n normalement).

Maintenant, il y a dans les tutos un article de Barbudor (pub gratuite) sur l'implémentation de protocole de communication. Tu pourrais peut être y trouver des choses intéressantes.

Merci grace à vous deux j'ai pu corriger mon code et ça fonctionne.
J'ai comme vous me l'avez conseillé déclaré l'index i et le tableau command en global comme ça ils ne sont pas réinitialisés à chaque tour de boucle, le i ne se remet à 0 que lorsque que in == ';' (quoique ça fonctionne aussi avec le '\n') mais j'aime bien comme ça) et je vais tout de suite implémenter en if pour ne pas faire d'overflow si on tape une commande trop longue.

Je voudrais savoir si on peut vider la pile de données entrantes, comme ça si i > cmdLen (longueur du tableau command), alors je vide la pile et le tableau en envoyant un message "commande trop longue"

Pour ceux que ça interesse le code fonctionnel :

boolean ledState[13] = {false};
int i = 0;
char cmd[50] = "";
int cmdLen = 50;

void setup () {
  
   pinMode (13, OUTPUT);
   Serial.begin (9600); 
}

void loop () {
  
  while (Serial.available ()) {
    char in = Serial.read ();
    
    if (i < 50) {
      cmd[i] = in;
   
      if (in == ';') {
        exeCmd ();
        clearCmd();
        break;
      }
    }
    else {
      Serial.println ("Commande trop longue.");
      Serial.flush ();
      clearCmd ();
    }
    
    ++i;
  }
  
}

void clearCmd () {
  for (int c; c < cmdLen; ++c)
    cmd[c] = 0;
  i = 0;
}
void exeCmd () {
  
  char * type = strtok (cmd, " ");
  
  if (strcmp (type, "Set") == 0) {
    // Action sur les sorties.
  }
  
  else if (strcmp (type, "Check") == 0) {
    // Vérifie l'état d'une sortie
    int num = atoi (strtok(NULL, " "));
    if (num > 0 && num < 13)
      Serial.println (ledState[num]);
    else
      Serial.println (String("Erreur la sortie ") + num + String(" n'existe pas"));
  }
  
  else if (strcmp (type, "Read") == 0) {
    // Lie la valeur d'une entrée
    int num = atoi (strtok(NULL, " "));
    if (num > 0 && num < 5)
      Serial.println (analogRead(num));
    else
      Serial.println (String("Erreur l'entree ") + num + String(" n'existe pas"));
  }  
  else if (strcmp(cmd, "\n") == 0)
    Serial.println ("Commande connue");
  else
    Serial.println ("Commande inconnue");
  
  
}

bonjour, pense a ajouter résolut dans le titre si ton problème est réglé :stuck_out_tongue:

Salut (re).

Je crois qu'il existe un truc genre Serial.flush(), à vérifier (peut-être que je confonds avec VB qui possède cette fonction...)

La fonction de Serial.flush() a changé depuis le passage à la version 1.0. Maintenant flush() attend que la file d'émission soit vide (comprendre complètement émise).
Pour vider la file de réception, il faut se faire sa propre fonction.
du genre

while(Serial.available()){Serial.Read()}