Carte SD - programmation [mettre en tableau key et val liées] - config.txt

Contrairement a d'autres langages comme PHP ou JavaScript, il n'est pas possible en C d'indéxer un tableau par une clé.

Il va falloir le faire "à la main"

Le plus simple est d'utiliser un tableau de structure :

#define NBKEY  20
#define KEYLEN 20
struct {
  char key[KEYLEN];
  int   val; // ou autre type
} TabKeyVal[NBKEY];

Ce qui te donne un tableau a 20 élements dans lequel tu peux mémoriser tes couples.
Par contre il faudra forcément chercher chaque fois que tu veux une valeur.
Par exemple

int getVal( const char *key, int *pVal )
{
  int i;
  for ( i = 0 ; i < NBKEY ; i++ )
    if ( 0  == strcmp( key, TabKeyVal[i].key )  )
    {
      *pVal = TabKeyVal[i].val;
      return 1;
    }
    return 0;
}

En C++ avancé il existe des moyens de faire des collections mais cela n'est pas disponible a ma connaissance sur ATmega.
Au mieux, tu peux encapsuler la structure et ses méthodes d'accès dans une classe.

Ok pour cette méthode.

Je viens de trouvé par hasard ceci : Arduino Playground - HashMap Library
Ça correspond à ce que je voulais faire mais hélas j'ai pas l'impression que ça fonctionne.

#include <HashMap.h>

#define SIZE_BUFFER 32  //Nombre de case du buffer

const int nbFic = 10;  //Nombre de fichier potentiel à scruter
const byte SIZE_HASH = 2;  //Nombre de hshtable

char bufferConf[SIZE_BUFFER];  //Le buffer des fichiers de conf
HashType<char*, String> hashRawArray[SIZE_HASH];
HashMap<char*, String> hashMapConfSvg = HashMap<char*, String>(hashRawArray, SIZE_HASH);

File myFile;  //Fichier
String tblFicMdle[nbFic];  //Le tableau des noms des fichiers modèles
String tblFicSvg[nbFic];  //Le tableau des noms des fichiers de sauvegardes
int nbFichierMdle;  //Nombre de fichier

/* SETUP */
void setup() {
//Initialiser la carte SD
  if(initSD()) {
    
    //Lister les fichier de sauvegardes
    listeFichier("/svgde/");
    
    //Lister les fichiers de configuration modèle
    nbFichierMdle = listeFichier("/modeles/");
    
    //Ouvrir le fichier de sauvegarde
    ouvrirFichier("/svgde/", tblFicSvg[0]);
  }
  else {
     
    //Charger les valeurs par défaut de la radio 
  }
}

/* LOOP */
void loop() {
}

/* FONCTION initLCD */
void initLcd() {
  
  //Initialisation de l'écran par I2C
  Wire.begin();  //Amorce la connection sur SDA et SCL
  Wire.beginTransmission(adresseLcd); //Appelle l'écran LCD
  Wire.write((byte)0x00);  //Assigne un null
  Wire.write((byte)0x0C);  //Effacer l'écran
  Wire.write((byte)0x13);  //Allume le rétro-éclairage
  Wire.write((byte)0x04);  //Ne pas afficher le curseur
  Wire.endTransmission();  //Ferme la connexion sur SDA et SCL
}

/* FONCTION ecrireLcd */
void ecrireLcd(String texte, int positionEcran) {
  
  //Initialisation de l'écran par I2C
  Wire.begin();  //Amorce la connection sur SDA et SCL
  Wire.beginTransmission(adresseLcd); //Appelle l'écran LCD
  Wire.write((byte)0x00);  //Assigne un null
  Wire.write((byte)0x02);  //Placer le curseur
  Wire.write(positionEcran);  //Sur l'écran
  Wire.print(texte);  //Ecrire un texte
  Wire.endTransmission();  //Ferme la connexion sur SDA et SCL
}

/* FONCTION ecrireLcdChar */
void ecrireLcdChar(char texte[32], int positionEcran) {
  
  //Initialisation de l'écran par I2C
  Wire.begin();  //Amorce la connection sur SDA et SCL
  Wire.beginTransmission(adresseLcd); //Appelle l'écran LCD
  Wire.write((byte)0x00);  //Assigne un null
  Wire.write((byte)0x02);  //Placer le curseur
  Wire.write(positionEcran);  //Sur l'écran
  Wire.write(texte);  //Ecrire un texte
  Wire.endTransmission();  //Ferme la connexion sur SDA et SCL
}

/* FONCTION clearLcd() */
void clearLcd() {
  
  //Initialisation de l'écran par I2C
  Wire.begin();  //Amorce la connection sur SDA et SCL
  Wire.beginTransmission(adresseLcd); //Appelle l'écran LCD
  Wire.write((byte)0x00);  //Assigne un null
  Wire.write((byte)0x0C);  //Effacer l'écran
  Wire.endTransmission();  //Ferme la connexion sur SDA et SCL
}

/* FONCTION initSD() */
boolean initSD() {
  
  clearLcd();  //Effacer l'écran
  ecrireLcd("Init SD Card", 01);  //Ecrire
  
  //Si la carte n'est pas présente ou qu'elle est en défaut
  if(!SD.begin(4)) {
    
    ecrireLcd("Non presente", 21);  //Ecrire
    return false;
  }
  
  ecrireLcd("Sd card... Ok", 21);  //Ecrire
  
  delay(1000);
  return true;
}

/* FONCTION listerFichier() */
int listeFichier(String chemin) {
  
  int count = 0;
  char path[SIZE_BUFFER];
  
  //Conversion du chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre ce qui est défini par le chemin
  myFile = SD.open(path);
  
  //Tant que c'est vrai
  while(true) {
    
    //Prend le nom du fichier
    File entry = myFile.openNextFile();
    
    //Si c'est la fin
    if(!entry) {
      
      break;  //Sortir de la boucle
    }
    
    //Si le nombre de fichier est plus important que le nombre de place en tableau
    if(count == nbFic) {
      
      break;  //Sortir de la boucle
    }
    
    //Si c'est le répertoire /modeles/
    if(chemin == "/modeles/") {
    
        //Rentre le nom de fichier dans le tableau des noms de modèles
        tblFicMdle[count] = entry.name();
    }
    
    //Si c'est le répertoire /sauvegarde/
    if(chemin == "/svgde/") {
        
      //Rentre le nom de fichier dans le tableau des noms de sauvegarde
        tblFicSvg[count] = entry.name();
    }
    
    count++; //Compter le nombre de fichier
  } 
  
  //Fermer le fichier
  myFile.close();
  
  //Retourner le nombre de fichier
  return count;
}

/* FONCTION qui ouvre un fichier */
void ouvrirFichier(String chemin, String fic) {
  
  //Variables
  char path[SIZE_BUFFER], *key, *val;
  int i, bufferLenght, lineCounter = 0;
  
  clearLcd();  //Effacer l'écran
  ecrireLcd("Fichier en lecture", 01);  //Ecrire
  
  //Re crée le chemin
  chemin = chemin + fic;
  
  //Transforme le chemin en tableau de carractères
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre le fichier de configuration
  myFile = SD.open(path, FILE_READ);

  //Erreur d'ouverture du fichier
  if(!myFile) {
    
    clearLcd();  //Effacer l'écran
    ecrireLcd("Pb fichier", 01);  //Ecrire
    for(;;); //Boucle infini (peut tre remplacer par un bouton valider)
  }
  
  //Tant que ce n'est pas la fin du fichier
  while(myFile.available()) {
    
    //Récupère la ligne
    i=0;
    while((bufferConf[i++] = myFile.read()) != '\n') {
      
      //Si la ligne dépasse le buffer
      if(i == SIZE_BUFFER) {
      
        //Finir la ligne et stopper la lecture
        while(myFile.read() != '\n') {
          break;
        } 
      }
    }
    
    bufferLenght = i;  //Garder en mémoire le nombre de char
    bufferConf[--i] = '\0';  //Finalise la chaine en virant le \n
    ++lineCounter; //Incrémenter le nombre de ligne
    
    //Ignore les lignes vides, les commentaires # et //
    if(bufferConf[0] == '\0' || bufferConf[0] == '#' || bufferConf[0] == '//') {
    
      continue;
    }
    
    //Cherche l'emplacement de la clef en ignorant les espaces et tabulations
    i = 0;
    while(bufferConf[i] == ' ' || bufferConf[i] == '\t') {
        
      //Ignore les lignes contenant uniquement des espaces ou des tabulations
      if(++i == bufferLenght) {
          
        break;
      }
    }
    //Gère les lignes mal formés
    if(i == bufferLenght) {
      continue;
    }
    key = &bufferConf[i];
    
    //Cherche l'emplacement du séparateur
    while(bufferConf[i] != '=') {
        
      //Ignore les espaces et tabulations
      if(bufferConf[i] == ' ' || bufferConf[i] == '\t') {
            
        bufferConf[i] = '\0';
      }
        
      //Ignore les lignes mal formés
      if(++i == bufferLenght) {
        break;
      }
    }
    
    //Gère les lignes mal formés
    if(i == bufferLenght) {
      continue;
    }
        
    bufferConf[i++] = '\0'; //Transforme me séparateur = en \o et continue
    
    //Cherche l'emplacement de la valeur en ignorant espaces et tabulations
    while(bufferConf[i] == ' ' || bufferConf[i] == '\t') {
          
      //Ignore les lignes contenant uniquement des espaces ou des tabulations
      if(++i == bufferLenght) {
        break;
      }
    }
    
    //Gère les lignes mal formés
    if(i == bufferLenght) {
      continue;
    }
    
    val = &bufferConf[i];

/*
  
String tblConfSvg[clef][valeur];  //Tableau de configuration Sauvegarde
String tblConfMdle[clef][valeur];  //Tableau de configuration valeur

*/    
    //Si c'est le répertoire /sauvegarde/
    if(chemin == "/svgde/") {
       hashMapConfSvg[lineCounter](key, val);
    }
    
    //Si c'est le répertoire /modeles/
    if(chemin == "/modeles/") {
    }
    
    Serial.println(lineCounter);
    Serial.println(key);
    Serial.println(val);
  }
  
  
  Serial.println(hashMapConfSvg.getIndexOf("Modele"), DEC);
  Serial.println(hashMapConfSvg.getValueOf("Modele"));
  
  
  //Fermer le fichier
  myFile.close();
  
  ecrireLcd("Fichier Ok", 21);  //Ecrire
  
  delay(1000);
}

Ceci alourdis pas mal le code le coup du hashtable. Mais tu a peut-être raison, ça m'a l'air aussi simple avec la recherche dans le tableau. Laquelle des deux solutions est la plus pratique / meilleure et surtout non consommatrice de ressources ?

Edit, je laisse tomber les hashtable. Beaucoup de souci d'upload à cause de cette librairie !

Retour à ta solution :grin:

Et si je chargais dans un premier tableau la clef, et dans un autre la valeur.

J'appelle la clef, qui me retourne sa position, avec la position je retrouve la valeur :wink:

Le tour serais joués sans se prendre la tête avec un tableau de structure XD

Un tableau de structure est juste plus propre du point de vue codage.
Sinon, avec 2 tableaux ca revient au même.

La, pour le moment mes tableaux sont vide malgres le fait que j'ai bien key et val qui ont des données.

Ok pour le fait que ça soit plus propre. À ce moment la, j'ai peut être intérêt à basculer une partie de mes fonctions en classe.

Des mon retour, je regarde pourquoi mes tableaux de String restent vide et je passe aux classes.

De retour à la maison, je me rend compte que key et val sont des char*.

Donc il me faut les convertir vers char ou String. Je cherche comment !

Tout simplement avec le constructeur de la classe String :

char* charArray = "Hello world!";
String str = String(charArray);

++

String str = String(charArray);

Voici un très bon exemple de mauvaise utilisation de String

A droite du signe = tu appelles le constructeur de la classe String. Ce qui crée un objet String temporaire, alloue de la mémoire pour stocker la chaine de caractère, copie la chaine de caractère dans l'espace mémoire ainsi alloué.
A gauche du signe égal tu a la définition d'une variable str de type String. Ce qui crée l'objet String appelé str.
Le signe égal signifie qu'il y a copié du membre de droite vers le membre de gauche.
L'objet temporaire est copié dans l'objet str.
La classe String va donc allouer de la mémoire pour stocker la chaine de caractère qui est contenue dans l'objet temporaire, puis copier les caractères.
Une fois la copie effectue, l'objet temporaire n'est plus utile donc il est détruit.

Bref :

  • Perte d'efficacité car on copie 2 fois l'ensemble des caractères = perte de temps
  • Fragmentation inutile de la mémoire car on alloue pour rien une mémoire temporaire qui est ensuite libérée mais crée un trou dans la zone d'allocation dynamique de mémoire.

Un meilleur code est tout simplement :

String str = "chaine initiale";

Qui ne fait que l'allocation de str et une seule copie.

Voilà pourquoi je considère qu'utiliser String c'est MAL ]:slight_smile:

Quand on est débutant et qu'on ne comprend pas trop les mécanismes qui sont derrière, les programmes plantent sans qu'on sache pourquoi : que croyez vous qu'il se passe si String essaye de redimensionner l'espace de stockage allouée à une String et qu'il n'y a plus de mémoire dispo : rien et le programme part dans les choux.
Sur un PC avec la mémoire virtuelle, peut de chance que cela arrive, mais au pire vous avez une jolie boite de dialogue : "Cette application s'est inopinément arretée : Pas assez de mémoire"

Malheureusement c'est quand on est débutant qu'on se laisse tenté par la facilité d'utilisation de String...

Par ailleurs sur PC, les classes String équivalents sont bien codées. On utilise ce genre de classe avec du try/catch pour pouvoir intercepter les exceptions. Ce qui n'est pas possible avec la classe String Arduino car les codeurs (ah ces artistes) ont allègrement oublié de lever des exceptions en cas d'erreur, se contentant de ne rien faire.

Sur un système à faible ressource mémoire tel qu'un ATmega, il vaut beaucoup mieux apprendre à gérer sa mémoire proprement en sachant ce que l'on fait plutôt que de laisser du mauvais code le faire dans son dos. A terme vous aurez des programmes plus fiables et plus économes en mémoire.

C'était la minute du Professeur Rollin XD

Très bonnes indications !
Justement je vais priofiter de ton cours pour rebondir. La mauvaise habitude que j'ai eu avec les String m'est resté :grin: Honte à moi !

Donc vers quelle type de variable s'orienté au niveau du final ?

Je demande car e viens de changer un peu la donne. En fait je vais me permettre de simplifier un peu tout ça.
Mon vichier va évoluer comme suit:

//Modèle
Robot Go

//Type
Robot

//Mix_V1V2 / Mix_V3V4
true
false

Par contre, au retour j'obtiens:

Robot Go
Robot
fals
false

Il manque un true et j'obtiens un false incomplet qui prends la place du true :disappointed_relieved:

Au niveau code quelques changements pour essayer. Tant que j'en suis à tester, autant que je teste toutes les solutions avant de me jetter dans une version finale.

#define SIZE_CONF 10  //Nombre de configuration par fichier

File myFile;  //Fichier
String tblFicMdle[SIZE_MODELE];  //Le tableau des noms des fichiers modèles
String tblFicSvg[SIZE_MODELE];  //Le tableau des noms des fichiers de sauvegardes
int nbFichierMdle;  //Nombre de fichier

/* SETUP */
void setup() {
//Initialiser la carte SD
  if(initSD()) {
    
    //Lister les fichier de sauvegardes
    listeFichier("/svgde/");
    
    //Lister les fichiers de configuration modèle
    nbFichierMdle = listeFichier("/modeles/");
    
    //Ouvrir le fichier de sauvegarde
    //ouvrirFichier("/svgde/", tblFicSvg[0]);
    
    ouvrirFichier("/modeles/", tblFicMdle[2]);
  }
}
else {
     
    //Charger les valeurs par défaut de la radio 
}

/* FONCTION listerFichier() */
int listeFichier(String chemin) {
  
  int count = 0;
  char path[SIZE_BUFFER];
  
  //Conversion du chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre ce qui est défini par le chemin
  myFile = SD.open(path);
  
  //Tant que c'est vrai
  while(true) {
    
    //Prend le nom du fichier
    File entry = myFile.openNextFile();
    
    //Si c'est la fin
    if(!entry) {
      
      break;  //Sortir de la boucle
    }
    
    //Si le nombre de fichier est plus important que le nombre de place en tableau
    if(count == SIZE_MODELE) {
      
      break;  //Sortir de la boucle
    }
    
    //Si c'est le répertoire /modeles/
    if(chemin == "/modeles/") {
    
        //Rentre le nom de fichier dans le tableau des noms de modèles
        tblFicMdle[count] = entry.name();
    }
    
    //Si c'est le répertoire /sauvegarde/
    if(chemin == "/svgde/") {
        
      //Rentre le nom de fichier dans le tableau des noms de sauvegarde
      tblFicSvg[count] = entry.name();
    }
    
    count++; //Compter le nombre de fichier
  } 
  
  //Fermer le fichier
  myFile.close();
  
  //Retourner le nombre de fichier
  return count;
}

/* FONCTION qui ouvre un fichier */
void ouvrirFichier(String chemin, String fic) {
  
  //Variables
  char path[SIZE_BUFFER], bufferConf[SIZE_BUFFER];
  int i, bufferLenght, lineCounter = 0;
  String toto[SIZE_CONF];
  
  clearLcd();  //Efface l'écran
  ecrireLcd("Fichier en lecture", 01);  //Ecrire
  
  //Crée le chemin
  chemin = chemin + fic;
  
  //Transforme le chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre le fichier
  myFile = SD.open(path, FILE_READ);
  
  //Si le fichier est présent
  if(myFile) {
    
    //Tant que ce n'est pas la fin du fichier
    while(myFile.available()) {
      
      
      //Récupère la ligne
      i = 0;
      while((bufferConf[i++] = myFile.read()) != '\n') {
        
        //Si la ligne dépasse le buffer
        if(i == SIZE_BUFFER) {
          
          //Finir la ligne et stopper la lecture
          while(myFile.read() != '\n') {
            
            break;
          }
        }
      }
      
      
      bufferLenght = i;  //Garder en mémoire le nombre de lettre
      bufferConf[--i] = '\0';  //Finir la ligne en retirant le saut de ligne
      
      //Serial.println(bufferConf[0]);
      //Ne pas enregistrer les lignes vides ou les commentaires
      if(bufferConf[0] == '\0' || bufferConf[0] == '/' || bufferConf[0] == '#') {
        continue;
      }
      
      toto[lineCounter] = bufferConf;
      ++lineCounter; //Compter le nombre de lignes
    }
    
    
    Serial.println(path);
    Serial.println(toto[0]);
    Serial.println(toto[1]);
    Serial.println(toto[2]);
    Serial.println(toto[3]);
    Serial.println(toto[4]);
    Serial.println(toto[5]);
    Serial.println(lineCounter);
    
    
  }
  else {
    
    clearLcd();  //Efface l'écran
    ecrireLcd("Fichier introuvable", 01);  //Ecrire
    for(;;);
  }
  
  //Fermer le fichier
  myFile.close();
  
  ecrireLcd("Fichier Ok", 01);  //Ecrire
  
  delay(1000);
}

Comme on le voie, si j'ai un commentaire, on saute, si j'ai un saut de ligne on saute.

Ne pas tenir compte de la sortie car je demande plus haut vers quoi je devrais évolué pour ne pas forcément utilisé un String :grin:

Merci.

De mon point de vue, il vaut mieux utiliser des tableaux de char, de dimensions connues et maitrisées.
Utiliser les bonnes fonctions telles que strncpy() au lieu de strcpy() etc ... afin d'éviter les débordements de tableaux.

A la fin tu verra que finalement, les tableaux de char et la libC (stdio.h et string.h) offrent beaucoup de possibilités et un meilleur contrôle.

Bon j'ai un autre problème :frowning:

A prori je ne sort jamais de la boucle while(myFile.available()) {}

Même quand elle ne contient rien !!

Bon j'ai trouvé !

Effectivement pas bon les Strings :grin: mais pas bon du tout :blush:

Bon, alors j'ai modifier mon code ainsi (et je ne vous met pas tout car c'est que la fin qui change !

/* FONCTION initSD() */
boolean initSD() {
  
  clearLcd();  //Effacer l'écran
  ecrireLcd("Init SD Card", 01);  //Ecrire
  
  //Si la carte n'est pas présente ou qu'elle est en défaut
  if(!SD.begin(4)) {
    
    ecrireLcd("Non presente", 21);  //Ecrire
    return false;
  }
  
  ecrireLcd("Sd card... Ok", 21);  //Ecrire
  
  delay(1000);
  return true;
}

/* FONCTION listerFichier() */
int listeFichier(String chemin) {
  
  int count = 0;
  char path[SIZE_BUFFER];
  
  //Conversion du chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre ce qui est défini par le chemin
  myFile = SD.open(path);
  
  //Tant que c'est vrai
  while(true) {
    
    //Prend le nom du fichier
    File entry = myFile.openNextFile();
    
    //Si c'est la fin
    if(!entry) {
      
      break;  //Sortir de la boucle
    }
    
    //Si le nombre de fichier est plus important que le nombre de place en tableau
    if(count == SIZE_MODELE) {
      
      break;  //Sortir de la boucle
    }
    
    //Si c'est le répertoire /modeles/
    if(chemin == "/modeles/") {
    
        //Rentre le nom de fichier dans le tableau des noms de modèles
        tblFicMdle[count] = entry.name();
    }
    
    //Si c'est le répertoire /sauvegarde/
    if(chemin == "/svgde/") {
        
      //Rentre le nom de fichier dans le tableau des noms de sauvegarde
      tblFicSvg[count] = entry.name();
    }
    
    count++; //Compter le nombre de fichier
  } 
  
  //Fermer le fichier
  myFile.close();
  
  //Retourner le nombre de fichier
  return count;
}

/* FONCTION qui ouvre un fichier */
void ouvrirFichier(String chemin, String fic) {
  
  //Variables
  char path[SIZE_BUFFER], bufferConf[SIZE_BUFFER];
  int i, bufferLenght, lineCounter = 0;
  
  clearLcd();  //Efface l'écran
  ecrireLcd("Fichier en lecture", 01);  //Ecrire
  
  //Crée le chemin
  chemin = chemin + fic;
  
  //Transforme le chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre le fichier
  myFile = SD.open(path, FILE_READ);
  
  //Si le fichier est présent
  if(myFile) {
    //Tant que ce n'est pas la fin du fichier
    while(myFile.available()) {
      
      //Récupère la ligne
      i = 0;
      while((bufferConf[i++] = myFile.read()) != '\n') {
        
        //Si la ligne dépasse le buffer
        if(i == SIZE_BUFFER) {
          break;
        }
      }
      
      bufferLenght = i;  //Garder en mémoire le nombre de lettre
      bufferConf[--i] = '\0';  //Finir la ligne en retirant le saut de ligne
      lineCounter++; //Compter le nombre de lignes
      
      //Ne pas enregistrer les lignes vides ou les commentaires
      if(bufferConf[0] == '\0' || bufferConf[0] == '/' || bufferConf[0] == '#') {
        continue;
      }
      
      Serial.println(bufferConf);
      
    }
    
    //Fermer le fichier
    myFile.close();  
  }
  
  return; 
}

la ou je sort mon Serial.println(bufferConf); c'est là que je dois entrée en tableau. Ce que je cherche c'est à avoir
tbl[0] -> Robot Go
tbl[1] -> Robot
tbl[2] -> true
tbl[3] -> false

Il y aura des tests sur true et false au final. Donc c'est un point à étudier afin de ne pas le perdre de vue.

En fait ça fonctionne très bien. Beaucoup d'erreur ont été commise au niveau tableau. :disappointed_relieved:

La confusion entre un tableau de char ou x valait le nombre de possibilités et un tableau à deux dimensions qui lui a un nombre de possibilité de case contenant un nombre de caractères données.

L'incompréhension de strncpy qui après coup est effectivement une fonction rapide et pratique.

Je met ici la solution finale qui fonctionne.

#define SIZE_BUFFER 32  //Nombre de case du buffer
#define SIZE_CMDE 10  //Nombre de commandes

File myFile;  //Fichier
String tblFicMdle[SIZE_CMDE];  //Le tableau des noms des fichiers modèles
String tblFicSvg[SIZE_CMDE];  //Le tableau des noms des fichiers de sauvegardes
int nbFichierMdle;  //Nombre de fichier

char tblConfSvg[SIZE_CMDE][SIZE_BUFFER];  //Le tableau des configuration du fichier de sauvegarde
char tblConfMdle[SIZE_CMDE][SIZE_BUFFER]; //Le tableau des configurations du fichier de modèle

/* SETUP */
void setup() {
//Initialiser la carte SD
  if(initSD()) {
    
    //Lister les fichier de sauvegardes
    listeFichier("/svgde/");
    
    //Lister les fichiers de configuration modèle
    nbFichierMdle = listeFichier("/modeles/");
    
    //Ouvrir le fichier de sauvegarde
    //ouvrirFichier("/svgde/", tblFicSvg[0]);
    
    ouvrirFichier("/modeles/", tblFicMdle[2]);
  }
  else {
     
    //Charger les valeurs par défaut de la radio 
  }
  
  Serial.println(tblConfMdle[0]);
  Serial.println(tblConfMdle[1]);
  Serial.println(tblConfMdle[2]);
  Serial.println(tblConfMdle[3]);
  Serial.println(tblConfMdle[4]);
}

/* LOOP */
void loop() {
}


/* FONCTION listerFichier() */
int listeFichier(String chemin) {
  
  int count = 0;
  char path[SIZE_BUFFER];
  
  //Conversion du chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre ce qui est défini par le chemin
  myFile = SD.open(path);
  
  //Tant que c'est vrai
  while(true) {
    
    //Prend le nom du fichier
    File entry = myFile.openNextFile();
    
    //Si c'est la fin
    if(!entry) {
      
      break;  //Sortir de la boucle
    }
    
    //Si le nombre de fichier est plus important que le nombre de place en tableau
    if(count == SIZE_CMDE) {
      
      break;  //Sortir de la boucle
    }
    
    //Si c'est le répertoire /modeles/
    if(chemin == "/modeles/") {
    
        //Rentre le nom de fichier dans le tableau des noms de modèles
        tblFicMdle[count] = entry.name();
    }
    
    //Si c'est le répertoire /sauvegarde/
    if(chemin == "/svgde/") {
        
      //Rentre le nom de fichier dans le tableau des noms de sauvegarde
      tblFicSvg[count] = entry.name();
    }
    
    count++; //Compter le nombre de fichier
  } 
  
  //Fermer le fichier
  myFile.close();
  
  //Retourner le nombre de fichier
  return count;
}

/* FONCTION qui ouvre un fichier */
void ouvrirFichier(String rep, String fic) {
  
  //Variables
  char path[SIZE_BUFFER], bufferConf[SIZE_BUFFER+1];
  int i, bufferLenght, lineCounter = 0;
  String chemin;
  
  clearLcd();  //Efface l'écran
  ecrireLcd("Fichier en lecture", 01);  //Ecrire
  
  //Crée le chemin
  chemin = rep + fic;
  
  //Transforme le chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre le fichier
  myFile = SD.open(path, FILE_READ);
  
  //Si le fichier est présent
  if(myFile) {
    //Tant que ce n'est pas la fin du fichier
    while(myFile.available()) {
      
      //Récupère la ligne
      i = 0;
      while((bufferConf[i++] = myFile.read()) != '\n') {
        
        //Si la ligne dépasse le buffer
        if(i == SIZE_BUFFER) {
          break;
        }
      }
      
      bufferLenght = i;  //Garder en mémoire le nombre de lettre
      bufferConf[--i] = '\0';  //Finir la ligne en retirant le saut de ligne
      
      //Ne pas enregistrer les lignes vides ou les commentaires
      if(bufferConf[0] == '\0' || bufferConf[0] == '/' || bufferConf[0] == '#') {
        continue;
      }
      
      
      
      //Charger le tableau des configurations modèle
      if(rep == "/modeles/") {
        
        /*
          Copie le tableau contenant une commande, dans le tableau
          à deux dimensions qui est extérieur
        */
        strncpy(tblConfMdle[lineCounter], bufferConf, SIZE_BUFFER);
        bufferConf[SIZE_BUFFER] = '\0'; //Fini par un '\0'
      }
      
      //Charger le tableau des configurations sauvegardes
      if(rep == "/svgde/") {
        
        /*
          Copie le tableau contenant une commande, dans le tableau
          à deux dimensions qui est extérieur
        */
        strncpy(tblConfSvg[lineCounter], bufferConf, SIZE_BUFFER);
        bufferConf[SIZE_BUFFER] = '\0'; //Fini par un '\0'
      }
      
      lineCounter++; //Compter le nombre de lignes
    }
    
    //Fermer le fichier
    myFile.close();  
  }
  
  return; 
}

La prochaine étape va être de pouvoir charger les variables de tests. Avant je faisait des tests sur des strings. N'aurais-je pas intérêt à faire des tests sur des char fonction1, fonction2, fonction3... chargée par fonction1 = tbl1[0]; ... ?

Dans mon tableau j'ai entre autre du boolean (true ou false) qui doivent au final être tester. Je comprends bien qu'il me faudra faire de nouvelles conversions.

Pour comparer des chaines de caractères : strcmp() ou strcasecmp()
Le premier prend en compte la différence minuscules/MAJUSCULES
Le 2nd ne prend pas en compte la différence
Renvoie 0 si égalité ou -1/+1 si différent (dépend si l'un est avant l'autre dans l'ordre alphabétique)

Si tu veux comparer qu'une partie de la chaîne il y a aussi strncmp() et strncasecmp() qui ne compare que les n premiers caractères
Tout est là : avr-libc: <string.h>: Strings

Et bien, c'est super cette page 8) J'ai encore appris des choses :grin:

Alors, j'ai fait évoluer mon code et je doit en profiter pour vous citer dans mes soures :wink:
Je vous simplifie le code de façon à montrer que ce qui n'a de l'intérêt avec le sujet.

/*
  Auteur : Alexandre LABURE
  Aide DVP : Ragmaxone, sevyc64, Winjerome, Bousk
  Aide Arduino.cc : barbudor, SesechXP
  Création : le 30 octobre 2012
  Dernière modification : le 02 novembre 2012
  Version 1.0
*/

/* DEFINITIONS */
#define SIZE_BUFFER 32  //Nombre de case du buffer
#define SIZE_CMDE 10  //Nombre de commandes

File myFile;  //Fichier
String tblFicMdle[SIZE_CMDE];  //Le tableau des noms des fichiers modèles
String tblFicSvg[SIZE_CMDE];  //Le tableau des noms des fichiers de sauvegardes
int nbFichierMdle;  //Nombre de fichier

char tblConfSvg[SIZE_CMDE][SIZE_BUFFER];  //Le tableau des configuration du fichier de sauvegarde
char tblConfMdle[SIZE_CMDE][SIZE_BUFFER]; //Le tableau des configurations du fichier de modèle

//Configuration pour un modèle
String confNom, confType;
boolean confMix_V1V2, confMix_V3V4; 

/* SETUP */
void setup() {
//Initialiser l'écran LCD
  initLcd();  //Initialiser l'écran LCD
  ecrireLcd("Telecommande 433Mhz", 01);  //Ecrire
  ecrireLcd("Version 1.0", 24);  //Ecrire
  ecrireLcd("Bonjour", 66);  //Ecrire
  
  delay(2000);
  
  
  //Initialiser la carte SD
  if(initSD()) {
    
    //Lister les fichier de sauvegardes
    listeFichier("/svgde/");
    
    //Lister les fichiers de configuration modèle
    nbFichierMdle = listeFichier("/modeles/");
    
    //Ouvrir le fichier de sauvegarde
    ouvrirFichier("/svgde/", tblFicSvg[0]);
    
    //ouvrirFichier("/modeles/", "model001.txt");
    ouvrirFichier("/modeles/", tblConfSvg[0]);
    
    //Charger les valeurs sauvegardés
    confNom = tblConfMdle[0];
    confType = tblConfMdle[1];
    
    //Compare la configuration
    if(strcmp(tblConfMdle[2], "true") == 0) {
      //Si c'est vrai
      confMix_V1V2 = true;
    }
    else {
      //Si c'est faux
      confMix_V1V2 = false;
    }
    
    //Compare la configuration
    if(strcmp(tblConfMdle[3], "true") == 0) {
      //Si c'est vraie
      confMix_V3V4 = true;
    }
    else {
      //Si c'est faux
      confMix_V3V4 = false;
    }
  }
  else {
     
    //Charger les valeurs par défaut de la radio
    confNom = "Aucun nom";
    confType = "Aucun";
    confMix_V1V2 = false;
    confMix_V3V4 = false;
  }
}

/* LOOP */
void loop() {
}

/* FONCTION initSD() */
boolean initSD() {
  
  clearLcd();  //Effacer l'écran
  ecrireLcd("Init SD Card", 01);  //Ecrire
  
  //Si la carte n'est pas présente ou qu'elle est en défaut
  if(!SD.begin(4)) {
    
    ecrireLcd("Non presente", 21);  //Ecrire
    return false;
  }
  
  ecrireLcd("Sd card... Ok", 21);  //Ecrire
  
  delay(1000);
  return true;
}

/* FONCTION listerFichier() */
int listeFichier(String chemin) {
  
  int count = 0;
  char path[SIZE_BUFFER];
  
  //Conversion du chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre ce qui est défini par le chemin
  myFile = SD.open(path);
  
  //Tant que c'est vrai
  while(true) {
    
    //Prend le nom du fichier
    File entry = myFile.openNextFile();
    
    //Si c'est la fin
    if(!entry) {
      
      break;  //Sortir de la boucle
    }
    
    //Si le nombre de fichier est plus important que le nombre de place en tableau
    if(count == SIZE_CMDE) {
      
      break;  //Sortir de la boucle
    }
    
    //Si c'est le répertoire /modeles/
    if(chemin == "/modeles/") {
    
        //Rentre le nom de fichier dans le tableau des noms de modèles
        tblFicMdle[count] = entry.name();
    }
    
    //Si c'est le répertoire /sauvegarde/
    if(chemin == "/svgde/") {
        
      //Rentre le nom de fichier dans le tableau des noms de sauvegarde
      tblFicSvg[count] = entry.name();
    }
    
    count++; //Compter le nombre de fichier
  } 
  
  //Fermer le fichier
  myFile.close();
  
  //Retourner le nombre de fichier
  return count;
}

/* FONCTION qui ouvre un fichier */
void ouvrirFichier(String rep, String fic) {
  
  //Variables
  char path[SIZE_BUFFER], bufferConf[SIZE_BUFFER+1];
  int i, bufferLenght, lineCounter = 0;
  String chemin;
  
  clearLcd();  //Efface l'écran
  ecrireLcd("Fichier en lecture", 01);  //Ecrire
  
  //Crée le chemin
  chemin = rep + fic;
  
  //Transforme le chemin
  chemin.toCharArray(path, SIZE_BUFFER);
  
  //Ouvre le fichier
  myFile = SD.open(path, FILE_READ);
  
  //Si le fichier est présent
  if(myFile) {
    //Tant que ce n'est pas la fin du fichier
    while(myFile.available()) {
      
      //Récupère la ligne
      i = 0;
      while((bufferConf[i++] = myFile.read()) != '\n') {
        
        //Si la ligne dépasse le buffer
        if(i == SIZE_BUFFER) {
          break;
        }
      }
      
      bufferLenght = i;  //Garder en mémoire le nombre de lettre
      bufferConf[--i] = '\0';  //Finir la ligne en retirant le saut de ligne
      
      //Ne pas enregistrer les lignes vides ou les commentaires
      if(bufferConf[0] == '\0' || bufferConf[0] == '/' || bufferConf[0] == '#') {
        continue;
      }
      
      //Charger le tableau des configurations modèle
      if(rep == "/modeles/") {
        
        /*
          Copie le tableau contenant une commande, dans le tableau
          à deux dimensions qui est extérieur
        */
        strncpy(tblConfMdle[lineCounter], bufferConf, SIZE_BUFFER);
        bufferConf[SIZE_BUFFER] = '\0'; //Fini par un '\0'
      }
      
      //Charger le tableau des configurations sauvegardes
      if(rep == "/svgde/") {
        
        /*
          Copie le tableau contenant une commande, dans le tableau
          à deux dimensions qui est extérieur
        */
        strncpy(tblConfSvg[lineCounter], bufferConf, SIZE_BUFFER);
        bufferConf[SIZE_BUFFER] = '\0'; //Fini par un '\0'
      }
      
      lineCounter++; //Compter le nombre de lignes
    }
  }
  else {
    clearLcd();  //Efface l'écran
    ecrireLcd("Fichier introuvable", 01);  //Ecrire
    for(;;);
  }
  
  //Fermer le fichier
  myFile.close();
  
  return; 
}

Je vous ai épargné le loop() qui contient des choses qui n'ont plus rien à voir.

Je me suis fait un petit exemple avec strcmp() et qui fonctionne parfaitement. On test si c'est sur "true" et si oui, on en profite pour écrire true dans un boolean, sinon on écrit un false. C'est peut-être pas des plus optimisé mais ça fonctionne très bien.

Je profite d'aileur pour vous dire que je vais laisser tomber la carte SD quelques jours car j'aimerais nettoyé un peu mon code. Remplacer des strings inutiles par des char et revoir un peu l'architecture interne avec des classes. Par exemple créer une classe pour la partie LCD. Ainsi, je n'aurais plus à refaire initLcd(); planqué dans ma fonction. Elle sera directement dans la classe. Je pense qu'à terme ça peut faire gagner quelques octets et c'est pas non négligeable. Qu'en pensez-vous ?

Planquer le code ne le fait pas disparaitre. DOnc tu ne gagnera pas d'octets.

Mais structurer ton code en modules, avec ou sans classes, te fera gagner en lisibilité et maintenabilité.

Ok, au temps pour moi ! Quand je parlais de réduire, le voulais dire augmenter la lisibilité.

Ceci dit, j'aimerais que les classes soit externe à mon programme principal. Comment ça se passe ?

In crée les classes puis un coup de require() suffit ? Ou bien, faut-t-il gardé dans le seul fichier à charger ?

require() c'est du Java(Script)

En C++ tu crée :

  • Un fichier H de déclaration
#ifndef _MACLASSE_H_
#define _MACLASSE_H_

Class MaClasse
{
  public:
    MaClasse(); // Constructeur par défaut
    MaClasse( int p1, int p2 ); // Constructeur avec paramètres
    ~MaClasse(); // Destructeur

    int Toto( int p1 ); // Fonction membre publique Toto

  protected:
    void titi( int p2 ) ; fonction membre protégée

    int x;  // variable membre
    int y;
};

#endif
  • Un fichier CPP d'implémentation
#include "MaClasse.h"

MaClasse::MaClasse()
{
  // code du constructeur par défaut
  x = 0;
  y = 0;
}

MaClasse::MaClasse( int p1, int p2 )
{
  // code du constructeur par défaut
  x = p1;
  y = p2;
}

MaClasse::~MaClasse()
{
  // code du destructeur
}

int MaClasse::Toto( int p1 )
{
  return (x + p1) / (y + 1);
}

void MaClasse::titi( int p1 )
{
  x++;
  y--;
}

Pour créer les fichiers dans l'IDE Arduino, tu cherche la petite fleche vers le bas à droite de la barre d'onglet et tu choisit "Ajouter un onglet"
Tu enregistre tous les fichiers dans le même répertoire que ton INO
Et normalement quand tu double-clique sur le INO, tout se charge

Au début du INO tu mets tous les #include "xxx.h"

Ok pour tout ça 8)

Je propose d'utiliser une fonction dans l'éditeur qui est de cocher éditeur externe. J'ai un bon vieux Geany qui me sert pas mal comme éditeur. Par contre, je compilerais avec l'ide arduino.

Je te remercie pour tous ces conseils et infos. Par contre require') php et java le font, peut-être pas c++ en effet :sweat_smile:

Bon et bien, la semaine prochaine, je met mon soft par terre, j'externalise tout :grin: