La trappola di String

ciao,
lo so che String sarebbe meglio non usarla, ma non pensavo che desse problemi anche cosi'....

ho una String:
String Xtimestamp = "";
valorizzata da una funzione che restiuisce una stringa (ad uso timestamp) come questa:
17-06-2023 10:54:09.
faccio questa assegnazione:
sensorData[NRead].timestamp = Xtimestamp.c_str();
in questa struttura:

RTC_DATA_ATTR struct SensorData {
  float voltage;
  float temperature;
  float humidity;
  float soilTemperature;
  float soilMoisture;
  float lux;
  **String timestamp;**
} sensorData[12];

risultato: niente. blanc.
ho provato anche la semplice assegnazione e altri metodi, ma niente....
Ma e' cosi' complicato assegnare una stringa ad un'altra stringa?

Mi perplimo

Una istanza di string non ha dimensione conosciuta a compile-time

E quindi non può essere membro di struttura

O sbaglio?

Mmm ... facendo un po' di ricerche trovo:

Q: Can you use a string class object instead of a character array for the name member in a structure?

A: The answer is yes unless you are using an obsolete compiler that does not support initialization of structures with string class members.

Quindi, probabilmente, è cosa supportata da Arduino che usa una versione piuttosto aggiornata di gcc c++ :roll_eyes:

Guglielmo

Non mi è chiara una cosa ... nella struct tu dichiari un oggetto di tipo String di nome timestamp, ma poi usi la funzione c_str() su un oggetto, evidentemente anche lui di classe String ,per ottenere una stringa classica del 'C' (un char array) da assegnare ad ... un oggetto di classe String :crazy_face: ...
... il senso di tutto questo ?

Guglielmo

ciao ,
intanto grazie per la risposta.
Be' il senso e' che avevo letto da qualche parte che la funzione String in realta' era un wrapper di altre funzioni (non ricordo piu') e che in alcuni casi poteva dare dei problemi. Allora ho cercato un modo differente nella vana speranza che funzionasse. Ma come dicevo nel primo post, ne ho provate tante ma niente....
Ho centinaia di altre assegnazioni nel codice e funziona tutto. Chissa' cosa succede in quella funzione. Ho anche pensato a cambiare il nome del campo, magari timestamp e' una parola riservata o interferisce con qualcosa....

P.S: sono sempre piu' tentato dal micro python...magari e' piu' 'robusto'.... non so come dire...

Se non metti il codice completo, o almeno una versione ridotta dove si presenta lo stesso problema, non lo scopriremo mai

Ecco le parti salienti.....

RTC_DATA_ATTR int MAX_READINGS = 12;
RTC_DATA_ATTR struct SensorData {
  float voltage;
  float temperature;
  float humidity;
  float soilTemperature;
  float soilMoisture;
  float lux;
  String timestamp;
} sensorData[12];

RTC_DATA_ATTR int NRead = -1;
String Xtimestamp = "";

void insertReading() {
  float voltage = readVoltage();
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  float soilTemperature = readSoilTemperature(voltage);
  float soilMoisture = readSoilMoisture(temperature, humidity, soilTemperature, voltage, settings.cal_dry_1, settings.cal_wet_1, settings.cal_dry_2, settings.cal_wet_2);
  float lux = readLux(voltage);
  Serial.println(Xtimestamp);

  NRead++;
  if (NRead >= MAX_READINGS - 1) {
    NRead = MAX_READINGS - 1;
    for (int i = 0; i <= (MAX_READINGS - 2); i++) {
      sensorData[i].voltage = sensorData[i + 1].voltage;
      sensorData[i].temperature = sensorData[i + 1].temperature;
      sensorData[i].humidity = sensorData[i + 1].humidity;
      sensorData[i].soilTemperature = sensorData[i + 1].soilTemperature;
      sensorData[i].soilMoisture = sensorData[i + 1].soilMoisture;
      sensorData[i].lux = sensorData[i + 1].lux;
      sensorData[i].timestamp = sensorData[i + 1].timestamp;
    }
  }
  sensorData[NRead].voltage = voltage;
  sensorData[NRead].temperature = temperature;
  sensorData[NRead].humidity = humidity;
  sensorData[NRead].soilTemperature = soilTemperature;
  sensorData[NRead].soilMoisture = soilMoisture;
  sensorData[NRead].lux = lux;
  sensorData[NRead].timestamp = Xtimestamp.c_str();
}

String getCurrentDateTime() {
    if (WiFi.status() == WL_CONNECTED) {
    // Connessione WiFi stabilita con successo
    Serial.println("getcurrenttime: sono gia connesso al wifi----------------------------");
    timeClient.begin();                        // Inizializza il client NTP
    timeClient.setTimeOffset(settings.XFuso);  // Imposta lo sfasamento orario desiderato

    while (!timeClient.update()) {  // Aspetta finché l'ora corrente non è disponibile
      delay(1000);
      Serial.println("getcurrentdatetime:  Aspetto server NTP -------------------------------");
    }
    Serial.println("getcurrentdatetime:  ora corrente ottenuta da ntp -------------------------------");
    time_t now = timeClient.getEpochTime();  // Ottieni l'ora corrente
    struct tm timeinfo;                      // Struttura per memorizzare la data e l'ora

    // Converti l'ora corrente in una struttura tm
    gmtime_r(&now, &timeinfo);

    char buffer[20];  // Buffer per la stringa formattata
    sprintf(buffer, "%02d-%02d-%04d %02d:%02d:%02d",
            timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900,
            timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);

    return String(buffer);  // Restituisci la stringa formattata
  } else {
    Serial.println("getcurrenttime: no WiFi -----------------------------");
    return "";
  }
}

RTC_DATA_ATTR è una direttiva al compilatore che fa in modo che la variabile associata venga memorizzata in un'area di memoria che mantiene inalterato il valore durante lo sleep.

Tu però gli stai passando una variabile String di tipo dinamico ed il compilatore non è in grado di determinarne la dimensione (perché ancora indefinita) e quindi di riservare lo spazio di memoria necessario.

Qui stai assegnando ad una variabile String il valore di un puntatore a String e non è la stessa cosa :wink:

Comunque per un timestamp secondo me la scelta giusta del tipo variabile sarebbe un induceva unsigned long (epoch time) e non una stringa sia essa String o C string

il fatto di acvere un timestamp sottoforma di string e' per la comodita' di poterlo 'maneggiare' anche in varie altre parti di programma senza problemi , ma effettivamente, con qualche sforzo in piu' potrei convertirlo in string al momento opportuno.

Per quanto riguarda la struttura, effettivamente potrebbe essere che rtc_data_attr non riesca a gestire una stringa... forse sarebbe meglio una char[20] ad esempio e quindi lasciare l'assegnazione con Xtimestamp.c_str(); che in questo caso avrebbe piu' senso....

Non è questione di gestire, è semplicemente che il compilatore non sa quanto spazio allocare e quindi non funziona come ti aspetti.

Se usi un array a dimensione fissa ovviamente il problema non si pone, ma non puoi assegnare ad un array una stringa come vuoi fare tu.

Devi usare le funzioni di manipolazione delle C string: strcpy(), strcat() etc etc

si intendevo 'gestire' in quel senso. Beh adesso ci provo e ti faccio sapere. grazie

Ha funzionato! :slight_smile: Grazie 1000.

ho modificato la funzione cosi':

void insertReading() {
  float voltage = readVoltage();
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  float soilTemperature = readSoilTemperature(voltage);
  float soilMoisture = readSoilMoisture(temperature, humidity, soilTemperature, voltage, settings.cal_dry_1, settings.cal_wet_1, settings.cal_dry_2, settings.cal_wet_2);
  float lux = readLux(voltage);
  Serial.println(Xtimestamp);

  NRead++;
  if (NRead >= MAX_READINGS - 1) {
    NRead = MAX_READINGS - 1;
    for (int i = 0; i <= (MAX_READINGS - 2); i++) {
      sensorData[i].voltage = sensorData[i + 1].voltage;
      sensorData[i].temperature = sensorData[i + 1].temperature;
      sensorData[i].humidity = sensorData[i + 1].humidity;
      sensorData[i].soilTemperature = sensorData[i + 1].soilTemperature;
      sensorData[i].soilMoisture = sensorData[i + 1].soilMoisture;
      sensorData[i].lux = sensorData[i + 1].lux;
      //sensorData[i].timestamp = sensorData[i + 1].timestamp;
      strcpy(sensorData[i].timestamp, sensorData[i + 1].timestamp);
    }
  }
  sensorData[NRead].voltage = voltage;
  sensorData[NRead].temperature = temperature;
  sensorData[NRead].humidity = humidity;
  sensorData[NRead].soilTemperature = soilTemperature;
  sensorData[NRead].soilMoisture = soilMoisture;
  sensorData[NRead].lux = lux;
  strncpy(sensorData[NRead].timestamp, Xtimestamp.c_str(), sizeof(sensorData[NRead].timestamp) - 1);
  sensorData[NRead].timestamp[sizeof(sensorData[NRead].timestamp) - 1] = '\0';
  /*for (int i = 0;i<=MAX_READINGS -1; i++) {
  Serial.println(sensorData[i].timestamp);
}*/
}

col ciclo di for alla fine (asteriscato) sto vedendo tutto l'array man mano che si riempie con i vari timestamp.

Era da giorni che ci stavo su! Assolutamente subdolo (almeno per me) .
Ah , per sicurezza, ho aggiunto la scrittura dello '0' a fine stringa....visto che e' cosi' 'permaloso' .. :wink:

P.S. ho aggiunto anche questi include :

#include <String.h>
#include <cstring>

Non è necessario perché ci pensa l'istruzione strcpy()

Sarebbe stato necessario invece se per qualche recondito motivo avessi usato ad esempio memcpy() o la scrittura byte a byte.

Ritorno su queste 'benedette' string per una nuova 'stranezza' (che poi al solito, verra' fuori che stranezza non e')....

In breve, dichiaro una String con RTC_DATA_ATTR.
Assegno un valore.
vado in deep sleep.
Riparto dopo un tot, variabile azzerata!
Il bello e' che tutto il resto funziona, ma sto avendo il dubbio sul fattio della corretta gestione delle string in rtc memory....
mi pare aver letto qualcosa mesi fa in proposito. Ne sapete qualcosa cortesemente?

grazie

La risposta l'hai giàavuta al post #8 .... dimentica la classe String ed impara ad usare bene le stringhe classiche del 'C' (che sono dei char array).

Guglielmo

hai ragione Guglielmo, ho gia' provveduto ad un 'aggiramento' del problema.

Ad ogni modo, mi pare che la gestione delle stringhe & similia, e' un po' troppo macchinoso.
Stavo pensando di passare a micropython, perche' da una prima occhiata mi pare molto meno rigido su diversi aspetti, pur mantenendo una certa ovvia rigorosita' di linguaggio......

Avresti lo stesso identico problema anche usando il micropython perché il meccanismo di ritenzione delle variabili associate alla macro RTC_DATA_ATTR non ha nulla a che fare con il linguaggio di programmazione utilizzato.

Se vuoi utilizzare il micropython perché ti piace di più, è una possibilità ed ovviamente sei liberissimo di sperimentare, ma questa scelta per come la vedo io non dovrebbe nascere da "antipatia" nei confronti delle istruzioni C/C++ per la manipolazione delle stringhe che sono la base da cui tutto poi si sviluppa.

1 Like

hai ragione, ma a volte si vive anche di 'antipatie ' e 'simpatie'....Faccio una battuta...: bei tempi il Clipper 87! facile come il basic e potente come il PL/1.... mah! :slight_smile:

Meglio ancora la versione successiva che ho usato per circa 4 anni.
Ma niente in confronto alla potenza del C/C++, forse in futuro il D oppure Rust ma al momento è così.

Però c'è da comprendere in profondità la differenza tra piattaforme embedded e PC e non è facile specie se ci si è limitati a scrivere programmi senza indagare sul funzionamento di un moderno PC incluso ovviamente il sistema operativo che è il punto chiave.

Abbiamo intrapreso da poco la strada che porta all'obbiettivo. Oggi non serve sapere come funziona un PC per potere scrivere un programma, ma un tempo non era così. L'obbiettivo è stato raggiunto per la maggior parte delle applicazioni che girano sul PC, ma per altre occorre sempre elevata competenza hardware/software di un PC.

Forse in futuro accadrà anche su piattaforme embedded altamente standardizzate, dove un artista scrive applicazioni senza nulla sapere di hardware, C string null terminated, FIFO, DMA, IRQ, ISR, allocazione dinamica della RAM ecc.

Ma per adesso pare evidente che non siamo ancora arrivati a questo punto.

Ciao.

3 Likes