Estrazione di una parte di stringa

Premetto che ho cercato ma non sono riuscito a capire e arrivare ad un risultato.
Via via seriale ricevo un payload che vado a scrivere in una stringa:

  String dati;
  for (int i = 0; i < length; i++) {
  char pippi = payload[i];
  dati += pippi;
  }
  Serial.print ("Dati ricevuti: "); 
  Serial.println (dati);

La stringa è una cosa simile a: Temperatura_nutella_nel_bicchiere@26.32

Avrei bisogno di ricavare le due stringe:
str1=Temperatura_nutella_nel_bicchiere
str2=26.32

In pratica il mio separatore è "@" ma lo posso cambiare a piacimento.

Leggevo (Guglielmo) che la gestione delle stringhe con piccole MCU poù causare problemi e di utilizzare un array di char. Corretto?
Nel mio caso come dovrei procedere? (considerando comunque che poi alla fine io ho bisogno di due stringhe).

Grazie

khriss75:
Leggevo (Guglielmo) che la gestione delle stringhe con piccole MCU poù causare problemi e di utilizzare un array di char. Corretto?

Corretto e, come sempre scrivo, ripeto anche a te ... NON sei su un PC dove c'è un sistema operativo ed un "garbage collector", sei su una piccola MCU con solo 2KBytes di SRAM, dove devi fare tutto tu e dove usare la classe "String", a causa dell'allocazione e riallocazione dinamica della memoria, porta quasi sempre ... a grossi problemi e sicuri mal di testa !!!

Impara ad usare le stringhe classiche del C ... ovvero semplici array di char terminati dal carattere null (0x00) e le funzioni che trovi nella libreria standard (... che, oltretutto, è automaticamente inclusa dal IDE) AVR libc ed, in particolare, quanto è in <string.h> dove hai TUTTO ciò che ti serve.

Guglielmo

1 Like

Accendendo la sfera di cristallo USB, osservando il tuo pezzo di codice intuisco che stai usando una libreria... direi PJON :slight_smile:
Tu hai già l'intero messaggio nell'array di char payload, puoi pensare di utilizzare le funzioni delle strighe classiche del C, come suggerito da Guglielmo, direttamente su tale array risparmiandoti il ciclo for e soprattutto l'oggetto String.
Quando detto vale se durante l'analisi del payload non invii nulla tramite la libreria (che in invio sovrascrive l'array).

fabpolli:
Accendendo la sfera di cristallo USB, osservando il tuo pezzo di codice intuisco che stai usando una libreria... direi PJON :slight_smile:

Metti in produzione la tua sfera di cristallo perché farai molti soldi!!! :smiley: :smiley: :smiley: In questo test sto proprio utilizzando la libreria PJON.

Le mie limitate conoscenze di programmazione però mi bloccano e non trovo il modo di fare quello che state consigliando. Non trovando un esempio di gestione "funzioni delle stringhe classiche del C" mi sono bloccato. Il mio payload è già bello che confezionato ma non so come "spacchettarlo" correttamente.

Quando detto vale se durante l'analisi del payload non invii nulla tramite la libreria (che in invio sovrascrive l'array).

Non ho capito bene questa parte, intendi dire che se dovessi inviare qualcosa di nuovo in risposta il payload (i dati in esso contenuti)verrebbe cancellato, corretto?

khriss75:
Non ho capito bene questa parte, intendi dire che se dovessi inviare qualcosa di nuovo in risposta il payload (i dati in esso contenuti)verrebbe cancellato, corretto?

Esatto, se fai un invio l'array payload viene sovrascritto, se hai questa necessità allora prima di tutto ti dovrai copiare il payload in un tuo array di char, potrai farlo con un for come già hai fatto ppure con la funzioen strncpy.
Altro suggerimento, per spezzarla puoi guardare questa funzione qui strtok.
Più in generale partendo dai link che ti abbiamo fornito troverai tutte le funzioni possibili ed imamginabili per gestire le stringhe classiche del C (nei miei link le trovi sulla spalla di sinistra), analizzale una ad una e vedrai che alla fine non è così difficile gestirle. Comunque in caso di difficolatà posta il codice che hai scritto qui e dettaglia il più possibile cosa non funziona così troverai sicuramente aiuto

Forse ho raggiunto il mio scopo... Però mi vergogno perché quando uno si documenta si rende conto di quanto l'ignoranza (ovviamente mia!!!) sia grande.

In pratica il codice si "riduce" a questo:

 char *parte1;
  char *parte2;
  parte1 = strtok (payload, "@");
  Serial.println (parte1);
 parte2 = strtok (NULL, "@");
 Serial.println (parte2);

Il risultato è questo:

10:52:43.224 -> Temperatura_nutella_nel_bicchiere
10:52:43.224 -> 25.2

Temo però di dover convertire le due variabili anche in stringa (anzi, la prima in stringa, la seconda in double) perché devo poi passarle al server MQTT con la funzione PubSubClient (lbreria PubSubClient.h) una cosa del genere:

client.publish(String(parte1).cstr(), String(parte2).c_str(), true);

In ogni caso ora devo vedere bene sto client.publish.

Beh, per ora un immenso GRAZIE e Buona Pasqua!

khriss75:
Temo però di dover convertire le due variabili anche in stringa (anzi, la prima in stringa, la seconda in double) perché devo poi passarle al server MQTT con la funzione PubSubClient (lbreria PubSubClient.h) una cosa del genere:

client.publish(String(parte1).cstr(), String(parte2).c_str(), true);

In ogni caso ora devo vedere bene sto client.publish.

Beh, per ora un immenso GRAZIE e Buona Pasqua!

se la libreria che usi è questa le funzioni accettano char array quindi nessuna conversione necessaria, se invece quella che usi non è quella e nei metodi per la pubblicazione è richiesto l'uso della classe String... beh io cambierei libreria :smiley:

khriss75:
La stringa è una cosa simile a: Temperatura_nutella_nel_bicchiere@26.32

A tutti i validissimi consigli che già ti hanno dato, aggiungo solo una cosa mia...

Se si parla di comunicazione tra PROGRAMMI, perché ci si ostina ad usare formati "per umani", che oltre ad essere più estesa (direi in questo caso inutilmente "logorroica" :slight_smile: ) diventa sempre più o meno difficile analizzare i dati ricevuti?

Diamine, hai un valore che immagino float con 2 cifre decimali (esatto?) e che andrà diciamo da 0 a 100 (se sono gradi...) da associare ad un determinato parametro, perché non usare dei semplici byte?? Il tuo messaggio diventa semplicemente 3 byte, uno per il parametro (che codificherai con una apposita #define) quindi sarà sempre payload[0], seguito, per il parametro della Nutella, da uno per i gradi (da 0 a 100 ma formalmente da 0 a 255 oppure se hai valori negativi sarà signed quindi da -128 a +127) ed uno per i centesimi di grado.

Una cosa del genere:

#define VAL_NUTELLA 1

switch ( payload[0] )
{
  case VAL_NUTELLA:
    float gradi = payload[1] + (float)payload[2]/100;
    Serial.print ("Dati ricevuti: "); 
    Serial.print("Nutella=");
    Serial.println(gradi);
    break;
  case VAL_ALTROPARAMETRO:
  ...
}

Molto più semplice e lineare, non trovi? :wink: Niente strtok(), conversioni in stringa, NULL, eccetera...

Lasciamo che le macchine parlino la lingua delle macchine. :smiley:

fabpolli:
se la libreria che usi è questa le funzioni accettano char array quindi nessuna conversione necessaria, se invece quella che usi non è quella e nei metodi per la pubblicazione è richiesto l'uso della classe String... beh io cambierei libreria :smiley:

Si, la libreria è quella ed è proprio come dici tu. Ho controllato prima di pranzo e mi hai preceduto. Non devo convertire nulla.

@docdoc: nella mia ricerca per la soluzione, mi sono imbattuto in una discussione dove affermavi quello che hai scritto sopra. Condivido pienamente solo (ma questa non vuole essere una scusa!) per chi è ignorantotto come me, la visione di dati più "umani" sembra rendere la cosa più comprensibile.
Come "scusa", ma verrò smentito ( :smiley: ), c'è che poi devo inviare la stringa (char array) via mqtt dove necessito di una sintassi quasi umana.
Potrei inviare i dati da "arduino sensore" ad "arduino base" come dici tu, ma poi "arduino base" dovrà comunque creare una stringa (char array, oramai ho imparato!) da inviare via mqtt del tipo "temperatura_nutella_nella_tazza" xxx. Potrei avere cose del tipo "temperatura_acqua_nel_bicchiere" xxx, oppure, "densità_olio_zuppiera" xxx.
Insomma, sei gli "arduino sensore" inviano il dato machine like, poi "arduino base" dovrà comunque costruire la frasetta che a me serve.
Certo, sono assolutamente d'accordo con te che in trasmissione è meglio inviare l'essenziale.

khriss75:
Come "scusa", ma verrò smentito ( :smiley: ), c'è che poi devo inviare la stringa (char array) via mqtt dove necessito di una sintassi quasi umana.

Non credo, in MQTT io vedo che payload è un unsigned int ossia uint8_t:

uint16_t publishPacket(uint8_t *packet, const char *topic, uint8_t *payload, uint16_t bLen, uint8_t qos)

Di fatto uint8_t, unsigned char o byte è la stessa cosa, non vedo il problema a mandare pacchetti come quelli che ti ho suggerito, e non vedo da nessuna parte un obbligo di usare nomi.. Tra l'altro anche inutilmente lunghi, ad esempio se anche volessi usare stringhe non farei "temperatura_nutella_nella_tazza" ma "TNutellaTazza", o "tNutTaz" o "NT".

Cosa che comunque è assolutamente irrilevante dal punto di vista della programmazione perché quelli che sono dei "token" fissi è sempre bene definirli a priori come costanti, per evitare che in una parte del codice magari ti dimentichi un "_" o metti un carattere maiuscolo al posto del minuscolo ed ecco che non ti funziona più bene, per cui fare:

#define VAL_NUTELLA 1

oppure

#define VAL_NUTELLA "temperatura_nutella_nella_tazza"

o

#define VAL_NUTELLA "supercalifragilisticespiralidoso-e-uscimmo-a-riveder-le-stelle-abcdefghi"

Non cambia nulla, nel codice userai sempre il simbolo "VAL_NUTELLA".

da inviare via mqtt del tipo "temperatura_nutella_nella_tazza" xxx. Potrei avere cose del tipo "temperatura_acqua_nel_bicchiere" xxx, oppure, "densità_olio_zuppiera" xxx.

Appunto, in ENTRAMBI i codici userai (devi usare) le STESSE definizioni costanti dei token, ma quindi, ripeto, se anche volessi a tutti i costi mettere delle stringhe, a che ti serve farle così lunghe? Non sei TU a doverle interpretare, ma il destinatario che è sempre un programma, e, oltre a non usare inutilmente un canale relativamente limitato come performance e senza alcun controllo di errore, per lui è più semplice confrontare due byte che due stringone non ti pare? Ancora peggio per i valori numerici, che invece di convertirli ins tringa e mandare la loro rappresentazione, per poi ricevere una stringa e riconvertirla in un valore, si fa prima a mandare qualche byte che ricomponi nel valore originario (vedi il mio esempio, con un byte per i gradi interi ed uno per i centesimi, eviti un parsing perché basta sommarli dividendo il secondo per 100).

Diciamo che vuoi a tutti i costi mantenere le stringhe, almeno usa comunque una sintassi breve, tipo:
CCCVVVV
dove "CCC" sono 3 caratteri per il "comando", e "VVVV" sono 4 byte con il valore (che a seconda del comando riconverti in un int, o float, o quello che ti pare) e terminati con CR+LF ("\r\n").
Esempio:

"temperatura_nutella_nella_tazza@23.5" -> "TNT2350"
"temperatura_acqua_nel_bicchiere@18.25" -> "TAB1825"
"densità_olio_zuppiera@6" -> DOZ0600"
e così via...

Insomma, sei gli "arduino sensore" inviano il dato machine like, poi "arduino base" dovrà comunque costruire la frasetta che a me serve.

Se parli del Serial.print() se vedi il codice che ti ho mandato, lo fa, ed il dato lo leggi in 2 istruzioni di numero... :wink:

Fidati, un protocollo informatico va pensato per le macchine, non per gli umani. E questa cosa (unita all'uso delle costanti per definire i token del protocollo) è sempre più importante all'aumentare della complessità del programma e del protocollo...

Ma alla fine fai come vuoi, contento tu... :wink:

docdoc:
---mega cut---

Ma alla fine fai come vuoi, contento tu... :wink:

No no, non è questione di fare come voglio, quelli che indichi sono preziosissimi consigli e cercherò di seguirli! Ci mancherebbe!!! (anche perché condivido al 100% quel che dici!).

Le cose sono due:

  1. la mia ignoranza nella programmazione e quindi purtroppo cerco di muovermi su terreni meno impervi (vedi le lunghe stringe). Detto questo non vedo il motivo per cui in ogni caso non debba studiare e giungere al risultato in modo più coerente (a livello programmazione parlando).

  2. via MQTT invio il valore con la relativa stringa (lunga) ad un serverino (windows o Rasp) in modo che possa visualizzare direttamente il dato in modo "umano" e subito comprensibile.

Direi che inizialmente, per semplificarmi la vita ma nello stesso tempo mantenere un aspetto "human like", farei inviare dagli arduino sensore (credo via RS485) ad "arduino base" dati del tipo TNT2350 TAB1825 DOZ0600 (quasi l'essenziale). Arduino base, ricevuta l'info, verificato che il dato è corretto e rispondendo che ha ricevuto il dato (e qui avrò da lavorarci mica male!!!) semplicemente mi creerà la stringa "umana" da inviare via MQTT al server: "temperatura_nutella_nella_tazza@23.5"... "temperatura_acqua_nel_bicchiere@18.25" ecc.
In questo modo a livello di trasmissione invierei pochi byte ed a livello di invio al server (MQTT) avrei già delle stringhe pronte e comprensibili...

Riprendo la discussione perché passando da una scheda Arduino nano ad un nodemcu ottengo un errore in fase di compilazione.

Questo è lo sketch:

#include <PJON.h>

// PJON object
PJON<SoftwareBitBang> bus(44);

void receiver_function(uint8_t *payload, uint16_t length, const PJON_Packet_Info &packet_info) {

  char *parte1;
  char *parte2;
  parte1 = strtok (payload, "@");
  Serial.println (parte1);
 parte2 = strtok (NULL, "@");
 Serial.println (parte2);


 };

void setup() {
  bus.strategy.set_pin(12);
  bus.begin();

  bus.set_receiver(receiver_function);

  Serial.begin(115200);
};

void loop() {
  bus.receive(1000);
};

Ricapitolando (per non dover leggere i messaggi pregressi), una scheda invia un dato (esempio "tmp@22.5") arduino che riceve divide in due parti con separatore "@".
Lo sketch viene compilato senza problemi su arduino nano,mea, uno etc. ma caricandolo su un nodemcu ho questo errore:

invalid conversion from 'uint8_t* {aka unsigned char*}' to 'char*' [-fpermissive]

la parte evidenziata dall'errore è:

parte1 = strtok (payload, "@");

La libreria PJON non compatibile con esp8266? In tal caso sposto li la richiesta, ma dagli esempi che vedo PJON pare supportare senza problemi esp8266.

Prova con al seguente sintassi:

// cast esplicito da puntatore a tipo uint8_t  verso puntatore char
parte1 = strtok ((char*)payload, "@");

Diversamente si può aggirare l'errore aggiungendo l'argomento -fpermissive alla riga di comando che invoca il compilatore (ma con L'ide stile arduino non so come si fa).

PS: Per avr-gcc è possibile configurare il compilatore in modo che consideri il tipo char di default unsigned.
Mentre normalmente 'char' senza specificare altro di default è 'signed'.

Ciao.

Maurotec:
Prova con al seguente sintassi:

// cast esplicito da puntatore a tipo uint8_t  verso puntatore char

parte1 = strtok ((char*)payload, "@");

Compila! Grazie!
Se hai un secondo potresti spiegarmi il perché di questa sintassi?
Grazie ancora!

Se hai un secondo potresti spiegarmi il perché di questa sintassi?
Grazie ancora!

mmmmmh...mica si può spiegare in modo esaustivo in un post.
Ti fornisco altre info, così ti documenti.

La sintassi è prevista dallo standard sul linguaggio per cui su un manuale C/C++ sono dedicate più pagine al casting: Qui una breve trattazione da wikipedia.

Il compilatore è flessibile e pertanto gli possiamo dire di comportarsi un modo specifico anziché nel modo predefinito. Ad esempio il compilatore avrebbe potuto anche effettuare un cast implicito e sollevare un messaggio Warning anziché di errore. Con il Warning il programmatore è stato avvertito e la compilazione non si ferma, in ogni caso con l'errore il programmatore si accorge del problema è verifica anche se per caso ha commesso un errore.

Spetta al programmatore sapere se il cast esplicito sia corretto anche a run-time. Per dire, compila con successo, ma durante l'esecuzione quel cast crea problemi.

PS: io non so se può o meno creare problemi a run-time.

Ciao.

moh guarda che lui voleva solo sapere perché ha dovuto cast(r)are
per lo OP:
l'errore ti diceva che stavi usando un array di byte al posto di un array di char
"invalid conversion from 'uint8_t* {aka unsigned char*}' to 'char*' [-fpermissive]"
significa errata conversione tra puntatore a intero senza segno a 8 bit verso puntatore a carattere
castando il puntatore verso il tipo che serve si è risolto
non è che sempre si possa fare, ma in particolare tra questi due tipi (esattamente questi) ed in questo caso specifico dovrebbe andare bene (che poi vorrei sapere perchè la libreria deve usare byte....)

moh guarda che lui voleva solo sapere perché ha dovuto cast(r)are
per lo OP:

:slight_smile: Io ho capito che volesse sapere riguardo alla sintassi, infatti scrive

Se hai un secondo potresti spiegarmi il perché di questa sintassi?

Del resto se hai già avuto modo di usare il cast vuole dire conosci la sintassi, comunque
ora e anche più chiaro il motivo.