Troncage de variable sur un c_str()

Bonjour,

Je rencontre un soucis avec mon code. Je dois changer un string enregistré dans l'EEPROM en char* pour faire une publication MQTT.

En gros, je stock le topic MQTT dans l'EEPROM en fonction de 2 éléments : lieu et nom du capteur.
ensuite je lui ajoute le nom de la variable et cela donnee:
temperature_topic = cheminMQTT + nomcpt + "/temperature";
dans mon cas : temperature_topic = maison/bureau/capt/temperature
puis je fais un temperature_topic.c_str() mais la je n'obtiens que : maison/bureau

peu importe les variables utilisées, j'obtiens toujours que la première partie a savoir cheminMQTT

Voici le code très simplifié pour illustrer ce que je dis (pas besoin de charger les capteurs et librairies anexes)

#include <EEPROM.h>

float temp = 0.0;

//Config MQTT
String nomcpt,cheminMQTT;
//Chemin MQTT
String temperature_topic; 
//MQTT
long lastMsg = 0;   

void readEeprom(){
  Serial.println("Lecture de l'EEPROM");
  for (int i = 0; i < 32; ++i) { nomcpt += char(EEPROM.read(i)); }
  Serial.print("nom capteur: ");
  Serial.println(nomcpt);
  
  for (int i = 128; i < 256; ++i){ cheminMQTT += char(EEPROM.read(i)); }
  Serial.print("cheminMQTT: ");
  Serial.println(cheminMQTT);
  temperature_topic = cheminMQTT + nomcpt + "/temperature";
  Serial.println(temperature_topic);
}

void setup() {
     Serial.begin(115200);
     EEPROM.begin(512);
     readEeprom();
}

void loop(){
  long now = millis();
 
  if (now - lastMsg > 1000 * 5) {
    lastMsg = now;   
    temp += 1.1;
    Serial.println(temperature_topic);
    Serial.print(temperature_topic.c_str());
    Serial.print(" : ");
    Serial.println(String(temp).c_str());  
    delay(25);
  }
}

et la réponse du moniteur série :
maison/bureau/capt/temperature
maison/bureau : 1.10

Ces lignes lisent trop de caractères

for (int i = 0; i < 32; ++i) { nomcpt += char(EEPROM.read(i)); }
for (int i = 128; i < 256; ++i){ cheminMQTT += char(EEPROM.read(i)); }

Et elles placent des informations invalides dans les String.
Il faudrait sortir de la boucle lorsque le terminateur de chaine est lu. Pour autant que tu ais écrit une c_string dans l'EEPROM.
Ce qui donnerait quelque chose comme ça

for (int i = 0; i < 32 && EEPROM.read(i)!=0 ; ++i) { nomcpt += char(EEPROM.read(i)); }
for (int i = 128; i < 256 && EEPROM.read(i)!=0; ++i){ cheminMQTT += char(EEPROM.read(i)); }

J'ai simulé la lecture en EEPROM par des tableaux de char. Si je ne mets pas la sortie des 2 boucles sur la lecture du 0 j'ai un comportement semblable à ce que tu observes.

//#include <EEPROM.h>

float temp = 0.0;

char tabnomcpt[32] = {'c','p','t','\0'};
char tabnomqtt[128] = {'m','a','i','s','o','n','/','b','u','r','e','a','u','/','\0'};
//Config MQTT
String nomcpt,cheminMQTT;
//Chemin MQTT
String temperature_topic; 
//MQTT
long lastMsg = 0;   

void readEeprom(){
  Serial.println("Lecture de l'EEPROM");
//  for (int i = 0; i < 32; ++i) { nomcpt += char(EEPROM.read(i)); }
  for (int i = 0; i < 32 && tabnomcpt[i]!=0; ++i) { nomcpt += tabnomcpt[i]; }
  Serial.print("nom capteur: ");
  Serial.println(nomcpt);
  
//  for (int i = 128; i < 256; ++i){ cheminMQTT += char(EEPROM.read(i)); }
  for (int i = 0; i < 128 && tabnomqtt[i]!=0; ++i){ cheminMQTT += tabnomqtt[i]; }
  Serial.print("cheminMQTT: ");
  Serial.println(cheminMQTT);
  temperature_topic = cheminMQTT + nomcpt + "/temperature";
  Serial.println(temperature_topic);
}

void setup() {
     Serial.begin(115200);
//     EEPROM.begin(512);
     readEeprom();
}

void loop(){
  long now = millis();
 
  if (now - lastMsg > 1000 * 5) {
    lastMsg = now;   
    temp += 1.1;
    Serial.println(temperature_topic);
    Serial.print(temperature_topic.c_str());
    Serial.print(" : ");
    Serial.println(String(temp).c_str());  
    delay(25);
  }
}

et j'obtiens ça

Lecture de l'EEPROM
nom capteur: cpt
cheminMQTT: maison/bureau/
maison/bureau/cpt/temperature
maison/bureau/cpt/temperature
maison/bureau/cpt/temperature : 1.10
maison/bureau/cpt/temperature
maison/bureau/cpt/temperature : 2.20

Je pense qu'il y a vraiment des c_string dans l'EEPROM ce qui expliquerait que print affiche correctement la chaine. Mais String doit se planter si on lui met n'importe quoi au-delà du terminateur de chaine.

A noter, on dit assez souvent sur le forum que les String sont à éviter car elles morcellent la mémoire et sont la cause de pas mal de problèmes.
On peut généralement s'en passer. Dans ton cas tu les utilises pour concaténer des chaines, tu pourrais le faire autrement:

  • soit en utilisant des c_string, c'est un peu plus contraignant mais c'est plus sur en utilisant strcat par exemple
  • soit en écrivant les morceaux les uns après les autres
    Au lieu de faire
String toto, titi, tutu, tata;
.....
toto = titi + tutu + tata;
Serial.print(toto);

tu peux faire

Serial.print(titi);
Serial.print(tutu);
Serial.print(tata);

Effectivement, la lecture des 128 octet de l'EEPROM avec une grosse majorité de 0 posait problème.
à la lecture de l'EEPROM j'ai fait un

for (int i = 128; i < 256; ++i)
    {
      if (EEPROM.read(i) != 0){
      cheminMQTT += char(EEPROM.read(i));
    }}

par contre il ne faut pas que j'utilise de 0 dans le champ la, mais ça ne devrait pas me poser de soucis. Est ce qu'il y a un character de fin de ligne? ou alors je le saisie mal dans l'écriture de l'EEPROM

          for ( i = 0; i < fcheminMQTT.length(); ++i)
            {
              EEPROM.write(128+i, fcheminMQTT[i]);
              Serial.print("Wrote: ");
              Serial.println(fcheminMQTT[i]); 
              j=i+128;
            }  
            EEPROM.write(j+1, '\0');
          EEPROM.commit();

En tout cas un grand merci à toi, c'est le dernier point qui me bloquait, le reste ne sera que de l'amélioration du code et du bonhomme :slight_smile:

fcheminMQTTcontient du texte donc il ne devrait pas y avoir 0, sauf comme terminateur. Le caractère 0 n'est pas codé par la valeur 0 dans une chaine de caractères.

Ta fonction qui relit l'EEPROM ne fonctionnera plus si la chaine en EEPROM change de longueur.
Exemple:
J'utilise \0 pour indiquer la fin de la chaine
tu écris en EEPROM

ceciestletreslongchemin\0

Si plus tard tu remplaces la chaine en question par celle-ci

ceciestlachaine\0

tu auras en EEPROM

ceciestlachaine\0ngchemin\0

et tu liras

ceciestlachainengchemin

puisque tu ignore les 0
Il faut sortir de la boucle de lecture lorsque tu rencontres le premier 0

for (int i = 128; i < 256 && EEPROM.read(i) != 0; ++i)
    {
      cheminMQTT += char(EEPROM.read(i));
    }

Ok, je n'avais pas ce soucis puisqu'avant de faire une écriture de mes valeurs je faisais un RaZ de l'EEPROM

for (int i = 0; i < 512; ++i) { EEPROM.write(i, 0); }

Mais ta solution est quand même vachement plus élégante et moins destructrice pour mon EEPROM (que je ne vais pas mettre a jour tous les 4 matins)