Fonction strtok

Bonjour , encore moi avec ma chaudière !

j'ai débusquer un petit sketch qui permet de faire un log des data reçue sur un port com , ça tombe bien c'est pile poil se que je cherché ! mais je me suis vite rendue compte que la taille devenait assez énorme en 1h donc pour faire des log sur 24h ou plus ...

je me suis donc demander de quoi j'avais besoin au minimum?
pour info je reçois environ 30 valeur de la chaudière (30 index) sous cette forme

Kesseltemp.;0126;2;2;°C;

en fouinant sur le fofo j'ai lue un post sur la fonction strtok et je me suis dit "ça c'est se qu'il te faut!"
resultat j'arrive à juste avoir.

2;63

soit l'index et la vrai valeur cela fait déja une économie de place sur la uSD

voici le code que j'ai trouvé et un peut adapter .

/*

In your program, you would read stuff into a char array.
For purposes of testing strtok, I'll just set it up
a "string" in an initialized array.

*/

char arr[30] = {
"Kesseltemp.;0126;2;2;°C;"
};

void setup()
{
Serial.begin(9600);
Serial.println("Index;Valeur");
}

void loop()
{
char str[30];
int val, ind, div;
int errors = 0;

strcpy(str, arr);

// First is throwaway unless you want to do strcmp with "!ANG" or some such thing
char *chpt = strtok(str, ";");
if (chpt == NULL) {
Serial.println("First strok returns NULL");
++errors;
}

if (errors == 0) {
chpt = strtok(NULL, ";");
if (chpt == NULL) {
Serial.println("Second strok returns NULL");
++errors;
}
else {
val = atof(chpt);
}
}

if (errors == 0) {
chpt = strtok(NULL, ";");
if (chpt == NULL) {
Serial.println("Third strok returns NULL");
++errors;
}
else {
ind = atof(chpt);
}
}

if (errors == 0) {
chpt = strtok(NULL, ",\r\n");
if (chpt == NULL) {
Serial.println("Fourth strok returns NULL");
++errors;
// This is an input error: do something to handle it.
}
div = atof(chpt);
}
if (errors == 0) {

Serial.print(ind);
Serial.print(";");
Serial.print((val)/(div));
Serial.println();
}

delay(1000);
}

bon comme on peut le voir c'est facile dans se cas puisque au début c'est moi qui donne "Kesseltemp.;0126;2;2;°C;".
bon je débute alors je commence simple en essayant de comprendre (avec la grippe spo toujours facile :grin:)

le début d'une transmission commence par $ (24) chaque donnée est séparer par un retour chario (0D) et la fin se termine par 1A .
donc si j'ai bien compris àprés il faut etre sur d'avoir tous ce qu'il y'a entre $ et 1A on le met dans le buffer. ça sert à rien s'il manque des infos -> donc on jéte.

se que je comprend pas c'est comment faire quand on ne connait pas la taille du buffer ! en effet dans mon cas cela peut étre variable , la chaudière peut envoyer les données pour les 30 index comme une autre fois elle peut envoyer les données pour 15 index.

pour le moment la plus grande trame que j'ai capturer c'est:

$ Prêt;0019;1;1;zst;..Kesseltemp.;0126;2;2;°C;..Abgastemp.;0060;3;1;°C;..Abgastemp S;0070;11;1;°C;..Kesselstrg ;0091;4;1;%;..Primärluft ;0000;5;1;%;..Rest O2 ist;0000;6;10;%;..O2 Regler ;0100;7;1;%;..Sekundärluft;0000;8;1;%;..Saugzug Soll;0000;9;1;%;..SaugzugIst;0000;10;1;U;..Einschub Ist;0000;12;1;%;..O2 Regler Pell ;0031;13;1;%;..Füllstand: ;20700;14;207;%;..Ansauggeschw.;0454;15;100;m/s;..Strom Austrags;0595;16;1000;A;..Fühler 1;0149;17;2;°C;..Kesselsoll ;0080;18;2;°C;..Pufferoben ;0000;20;2;°C;..Pufferunten ;0000;21;2;°C;..Pufferpumpe ;8192;22;1;%;..Boiler 1;0124;23;2;°C;..Vorlauf 1;0036;24;2;°C;..Vorlauf 2;0038;25;2;°C;..HK Pumpe 1;0000;26;1; ;..HK Pumpe 2;0000;27;1; ;..Aussentemp;0030;28;2;°C;..Kollektortemp;0000;29;2;°C;..Betriebsstunden;1221;30;1;h;..Fehler;Kein Fehler ;99;1; ;...

aurriez vous des idées ou une piste à suivre?

merci au fofo .

Salut,

Je ne me nomme pas fofo mais j'ai pêtre un début de réponse:
http://www.ortholud.com/

Salut !

SORS DU CORPS DE MORIBOND :smiley:

Si tu connais bien la syntaxe des trames, peut importe qu'elles soient à longueur variable si tu implémente un automate.

Lit le flux caractère par caractère et en fonction du caractère reçu, déplace toi dans un arbre de décision.
Ecrit d'abord l'arbre à la main sur un papier avant de le coder.
N'oublie pas de traiter aussi les cas où le caractère reçu ne correspond pas à un caractère attendu (soit à cause d'une erreur de transmission, soit à cause d'une erreur dans ton arbre) sinon ton automate peut se retrouver bloqué.

bon , ça à avancée un petit peut :slight_smile:

quand au début du sketch on déclare :

char arr[30] = {
    "Kesseltemp.;0126;2;2;°C;"
};

le résulta attendue est : 2;63 et c'est bien se qui s'affiche. donc ok

il faut donc remplacer char arr[30] par les data qui arrivent via un port com (mega 2560)

me voila donc reparti à la pêche aux infos (je me suis commandé 2/3 livres d’occase sur l'arduino ).

avec se sketch on lit sur le serial puis on "parse" (désoler j'ai pas encore tous les termes en tête)

char inData[30];
byte index = 0;

void setup()
{
Serial.begin(57600);
}

void loop()
{
while(Serial.available() > 0)
{
char aChar = Serial.read();
if(aChar == '\r'|| index==30)
{
// End of record detected. Time to parse
Serial.print(inData)
index = 0;
inData[index] = NULL;
}
else
{
inData[index] = aChar;
index++;
inData[index] = '\0'; // Keep the string NULL terminated
}

}

}

donc si mes lectures et TRADUCTIONS (merci google) sont bonne:

dans le sketch qui teste la fonction strtok() nous avons
strcpy(str,inData);
ici on copie inData dans str , d’après ce que j'ai lue on pourrait utilisé strlcpy(str,inData,30) qui est moins "gourmand" mais bon déjà la .... on fait simple

à ce moment si je fait un Serial.print(str); c'est toujours ok le terminale affiche les données que j'ai envoyées.
mais après plus rien ne va plus , on passe à:

char *chpt = strtok(str, ";");
    if (chpt == NULL) {
        Serial.println("First strok returns NULL");
        ++errors;

et la j'ai un beau : First strok returns NULL

auriez vous une idée info ?
merci

Je ne vois pas ton code où tu utilises strtok
Le principe de strtok est que tu l'appelles dans une boucle. Lors du 1er appel le 1er paramètre est la chaine a parser, les suivants c'est null jusqu'à ce que tu arrive au bout

char separateurs[] = ",;\n\r";  // caractères pouvant séparer

parser_ma_chaine( char *ma_str )
{
  char *p;

  p = strtok( ma_str, separateurs )
  while ( p )
  {
    // un morceau a traiter
    Serial.println( p );


    // au suivant
    p = strtok( NULL, separateurs );
  }
  // finit
}

Attention:

  1. strtok modifie la chaine d'entrée au fur et a mesure qu'elle l'analyse
  2. strtok ne sait pas traiter correctement 2 séparateurs consécutifs
    Ainsi si en entrée tu as des champs optionnels tels que "aaa,bbb,,ddd"
    Tu va extraire "aaa", "bbb", et "ddd". Tu ne sauras qu'il y a un champ vide de perdu.

Voici une version de strtok qui permet de traiter les champs vides

char *strtok_barbudor( char *_string, const char *_sep )
{
  char *retptr;
  static char *savptr;

  if ( _string )
    savptr = _string;

  retptr = savptr;
  while ( savptr && *savptr && !strchr( _sep, *savptr ) )
    ++savptr;

  if ( savptr && *savptr )
    *savptr++ = '\0';
  else
    savptr = NULL;

  return retptr;
}

Avec "aaa,bbb,,ddd", cette version renvoie "aaa", "bbb", "" puis "ddd"

la fonction strtok qui "fonctionne" est dans le 1°post.

barbudor:
Si tu connais bien la syntaxe des trames, peut importe qu'elles soient à longueur variable si tu implémente un automate.

Lit le flux caractère par caractère et en fonction du caractère reçu, déplace toi dans un arbre de décision.
Ecrit d'abord l'arbre à la main sur un papier avant de le coder.
N'oublie pas de traiter aussi les cas où le caractère reçu ne correspond pas à un caractère attendu (soit à cause d'une erreur de transmission, soit à cause d'une erreur dans ton arbre) sinon ton automate peut se retrouver bloqué.

dans un lointain passé j'ai programmer des tsx17 ! mais vraiment c'est loin!!!!

dans mon actuelle ce que je désire enfin aimerais :smiley:

je viens d'installé une chaudière à pellet de marque froling , sur cette bécane il y'a 2 port RS232 .
port com1 , ou port de service . sur ce port on envoie une demande (désoler c'est dimanche) la P4 répond mais par se port on peut faire une vérification des données , mais aussi
on peut faire des bétises "touche pas à ça petit con!" donc pour le moment on oublie , pourquoi? manque de compétences et surtout manque d'infos! enfin les infos je les ai mais il faut les traduire via google et c'est de l'allemand ! donc traduction ...

le port com 2 quant à lui :smiley:

soit on le paramètre en MODBUS (bon encore on oublie) soit on le paramètre pour qu'il envoie les données en continue.

je suis un grand débutant en C++ / arduino et dans ce mode on ne peut pas faire de bêtises!!!

au départ j'ai voulu faire un log de toutes les data qui arrivées via ce port! mais une heure de "log" = 2 mo pfff.....

et sous open office ou excel la misère pour exploité les données.

donc mon but actuelle et de faire un log d'une trame complète soit environ 30 valeurs sous cette forme-> 1;45

ou 1 est l'index et 45 la valeur.

je vais bien étudier ta réponse pour comprendre , merci

Un automate n'est pas forcement un équipement. On parle d'ailleurs plutot d'automate industriel dans ce cas.
Mais une façon de programmer.
Typiquement tu utilise une variable globale d'état et sur gère chaque état dans un switch case.
Dans chaque cas, tu traite l'évenement pour passer dans un autre état

Par exemple

enum { ETAT1, ETAT2, ETAT3 } etat = ETAT1;

traite_evenement( ....)
{
  switch( etat )
  {
  case ETAT1:
    if ( evenement_X )
    {
      // traiter l'evenement1
      ....
      // puis passer dans l'etat 2
      state = ETAT2;
    }
    else if ( evenement_Y )
      // on passe direct en 3
      state = ETAT3;
    else
      // sinon on reste dans l'etat 1
      state = ETAT1; // inutile d'un point de vue code mais utile du point de vue autodocumentation du code
    break;
  case ETAT2:
    ......
    break;
  case ETAT3:
    .....
    break;
  }
}

raisonner par évènement et état permet un code plus facilement maintenable et debuggable que des if imbriqués de manière incompréhensible au delà des 3 minutes qui ont servit a les coder... :wink:

Mais finalement dans ton cas, tu n'en a pas forcement besoin si tu n'as pas besoin de traiter la chaîne de caractère au fil de l'eau (c'est à dire que tu as le temps de procéder en 2 étapes : capture puis analyse).
Dans certains cas, si les données arrivent en continue, tu peux avoir besoin de tout traiter à la volée.

En fait, tu as bien un petit automate à 3 états :

  1. attente du caractère de début '$'
  2. accumulation des caractères jusqu'a soit le compteur atteint le max, soit le caractère de fin est rencontré
  3. analyse de la trame

Un automate n'est pas forcement un équipement.

On parle aussi de machine à états. D'ailleurs c'est appellation que l'on retrouve généralement dans la littérature anglo-saxonne (state machine)

ça y est !

avec ce code j'arrive à lire les données qui arrivent sur le port com sous cette forme : Kesseltemp.;0126;2;2;°C
et après dans la console je lis juste 2;63

char inData[35]; // to store incoming data
byte index = 0;
char str[35]; // string to copy inData
int val, ind, divi;
int errors = 0;

void setup()
{
Serial.begin(57600); // star serial at 57600
Serial.println("index;valeur"); // juste one time for test *.CSV
}
void loop()

{

while(Serial.available() > 0)
{
char aChar = Serial.read();
if(aChar == '\r') // cariage return is the end of data
{
// End of record detected. Time to parse

strcpy(str,inData); // copy inData into str

/*
Serial.print("str="); // for debug
Serial.println(str);
Serial.print("inData=");
Serial.println(inData);
Serial.println();
*/
// First is throwaway unless you want to do strcmp with "!ANG" or some such thing
char *chpt = strtok(str, ";");
if (chpt == NULL) {
Serial.println("First strok returns NULL");
++errors;
}

if (errors == 0) {
chpt = strtok(NULL, ";");
if (chpt == NULL) {
Serial.println("Second strok returns NULL");
++errors;
}
else {
val = atof(chpt);
}
}

if (errors == 0) {
chpt = strtok(NULL, ";");
if (chpt == NULL) {
Serial.println("Third strok returns NULL");
++errors;
}
else {
ind = atof(chpt);
}
}

if (errors == 0) {
chpt = strtok(NULL, ";\r\n");
if (chpt == NULL) {
Serial.println("Fourth strok returns NULL");
++errors;
// This is an input error: do something to handle it.
}
divi = atof(chpt);
}
if (errors == 0) {

Serial.print(ind);
Serial.print(";");
Serial.print((val)/(divi));
Serial.println();
}

index = 0;
inData[index] = NULL;

}
else
{
inData[index] = aChar;
index++;
inData[index] = '\0'; // Keep the string NULL terminated

}
}
}

merci à tous