Parsing array di char (o alternativa)

Salve a tutti,

sono due giorni che sto sbattendo la testa su un semplicissimo problema di parsing di un array di char derivante da una delle tante librerie gsm; premetto che quasi sicuramente gran parte della colpa è la mia ignoranza nell'uso del linguaggio, a digiuno ormai da qualche anno. Comunque, di seguito il codice "incriminato", chiederei gentilmente se qualcuno vede qualcosa di strano ed eventualmente ha suggerimenti:

char buffer[64]; // buffer array for data recieve over serial port
int count = 0;
char *p;
char *arr[20];

void loop()
{
  if (Sim900Serial.available())             
  {
    while(Sim900Serial.available())         
    {
      buffer[count++]=Sim900Serial.read();     
      if(count == 64)break;
  }

  p = strtok(buffer,":,|/");
    while (p!=NULL)
    {
      arr[i++] = p;
    }

    
    Serial.write(arr,count);         
   
    count = 0;      
 
  }
if (Serial.available())           
    Sim900Serial.write(Serial.read());       
}

La parte relativa alla lettura della shield gsm, quella funziona, l'ho messa solo per far capire come viene costruito l'array di char (in realtà byte, ma se ho ben capito sono la stessa cosa) che voglio tokenizzare.
L'sms è strutturato in maniera ben definita, contiene di fatto dei comandi predefiniti i cui parametri sono separati da "|", una cosa del tipo

comando|par1|par2|par3

L'sms ricevuto è una cosa così:

+CMT: "+39346613xxx","","15/07/12,13:31:08+08"
comando|par1|par2|par3

Tutta la prima parte da +CMT a +08 non mi interessa, a me serve solo il corpo del messaggio, quindi da "comando" alla fine del messaggio.
Come vedete ho provato ad usare la funzione strtok(), il problema è che mi si blocca al while se poi cerco di scrivere il risultato su seriale.
Di fatto, se commento tutto il ciclo while e in Serial write metto buffer, a seriale mi compare il messaggio; se lo decommento non scrive niente, segno che a quanto pare si blocca in quel punto.
Chiedo quindi se c'è qualcosa di errato, oppure se semplicemente c'è un altro modo/funzione per fare ciò.
Aggiungo che quello che dovrei ottenere è una variabile di tipo char** array da passare ad una funzione, quindi una lista strutturata così lista *l = {par1,par2,par3}.

Grazie in anticipo

A occhio mi pare che i non sia inizializzato, ma forse mi sfugge dove lo hai fatto :frowning:

Un'altra cosa che proverei a verificare è la dichiarazione char *ptr[20] (non ricordo il nome della variabile perché non vedo l'originale mentre scrivo). La funzione di scomposizione in token non l'ho mai usata ma ipotizzo che se restituisce una stringa con il terminatore, abbia un buffer interno dove di volta in volta scrive il token. Se è così, questo buffer viene sovrascritto a ogni chiamata e non si potrebbe essere sicuri che l'elemento nell'array di puntatori stia puntando la cosa giusta. Una possibilità per provare è dichiarare qualcosa come:

#define BUFLEN 6
char arr[20][BUFLEN+1] ;

e usare strcpy incrementando l'indice principale. Questa ovviamente è solo un'altra possibile prova se il problema non è ancora altrove. Io di solito tendo a non fare nessuna ipotesi a me favorevole sull'implementazione delle funzioni a meno che non sia esplicitissimamente documentata e accettata come standard.

Giusto per allenarmi a usare i tag del forum, pensavo a qualcosa del genere:

char buffer[64]; // buffer array for data recieve over serial port
int count = 0;
char *p;
char *arr[20];

void loop()
{
  if (Sim900Serial.available())             
  {
    while(Sim900Serial.available())         
    {
      buffer[count++]=Sim900Serial.read();     
      if(count == 64)break;
  }

  static uint8_t   i = 0 ;
  p = strtok(buffer,":,|/");
    while (p!=NULL)
    {
      strcpy(arr[i],p) ;
       if(  ++i >= 20  )
       i = 0 ;
    }

    
    // Serial.write(arr,count);         
   
    count = 0;      
 
  }
if (Serial.available())           
    Sim900Serial.write(Serial.read());       
}

il buffer termina correttamente con \0?
il corpo del messaggio è separato da un \n?

la strtok va chiamata ogni volta:

char* p = buffer;
while ( *p && *p != '\n' ) ++p;
if ( !*p ) return errore....
++p;
byte i = 0;
arr[i++] = strtok(buffer,"|");
while ( i < 20 && (a[i++] = strtok (NULL,"|"))  );
....
while ( *p )
{
    arr[i++] = p;
    while( *p && *p != '|' ) ++p;
    if ( !*p ) break;
    *p++ = '\0'
}

questa invece è l'alternativa più performante alla strtok.

Intanto grazie del suggerimento...ti dirò la verità, non l'ho usato perchè penso di aver risolto il problema: sostanzialmente mancava una riga di codice, come esplicitato nella documentazione (penso ufficiale). Il codice quindi diventa (esplicito solo la parte di interesse, per chiarezza)

p = strtok(buffer,"|");
    while (p!=NULL)
    {
      Serial.println(p);
      arr[i++]=p;
      delay(2000);
      p = strtok(NULL,"|" );
    }

Serial.write(*arr,count);

I delay li ho inseriti perchè altrimenti mi mischiava le varie parti, non scrivendole a video nell'ordine corretto, probabilmente dovuto alla pesantezza della seriale...

Il problema però adesso è un altro: non scrive una parte finale del messaggio, di seguito un esempio di quello che succede:
Testo originale

+CMT: "+393466xxxxx","","15/07/12,21:03:18+08"
comando|123|456|78|901|23|4

Testo dopo strtok(buffer,"|")

+CMT: "+393466xxxxx","","15/07/12,21:06:07+08"
comando1234

Pensando fosse un problema di spazio negli array li ho anche aumentati, sia buffer sia arr a 100, ma con il medesimo risultato.

Suggerimenti?

Grazie mille

Saluti

vbextreme:

....

while ( *p )
{
    arr[i++] = p;
    while( *p && *p != '|' ) ++p;
    if ( !*p ) break;
    *p++ = '\0'
}



questa invece è l'alternativa più performante alla strtok.

Vedo adesso la tua risposta, grazie...
Scusa l'ignoranza ma mi sfugge uan cosa: nella tua versione, buffer ( e quindi il messaggio) dove viene elaborato? Oppure uso sempre strtok()??

Grazie

il secondo codice postato è un'alternativa s strtok quindi p punta esattamente all'inizio del corpo del messaggio.

Non hai risposto a nessuna delle mie die domande:
il corpo del messaggio è delimitato da\n?
buffer contiene il terminatore \0?

vbextreme:
il secondo codice postato è un'alternativa s strtok quindi p punta esattamente all'inizio del corpo del messaggio.

Non hai risposto a nessuna delle mie die domande:
il corpo del messaggio è delimitato da\n?
buffer contiene il terminatore \0?

Vero, scusa...

il corpo del messaggio è un sms in modalità testo, quindi viene visto esattamente come lo vedi esplicitato nei precedenti post..io manualmente non inserisco alcun delimitatore...

Ottimo, funziona anche questo ed è anche più pulito nella scrittura...penso anche di averlo capito (i puntatori mi mettono parecchio in difficoltà quindi ci metto un pò a entrare nella logica).

Adesso, se ho ben capito, arr è una matrice di stringhe, nel senso che dovrei trovarmi tante righe quanti sono i "token" delimitati da "|"; a me serve estrarre il comando e ,dipendentemente da questo chiamo la relativa funzione alla quale passo i parametri, cioè, in questo esempio, i vari gruppi di numeri delimitati.
Anche in questo caso presumo sia tutto un gioco di puntatori, quindi chiederei ulteriore aiuto per comprendere la cosa.

Anzi, pensandoci bene forse ci sarebbe la possibilità di semplificare le cose riducendo il range da esaminare: il messaggio infatti dovrebbe essere standard nella prima parte (in quanto a numero di caratteri "utilizzati"), quindi la parte di "buffer" interessante dovrebbe partire sempre dall'indice 47 (i caratteri da + a 8 della prima parte del messaggio sono infatti 47).

grazie

Ciao

Allora la prima cosa da fare è trovare se c'è un delimitatore tra l'header del messaggio e il suo body, da come l'hai scritto ci deve essere uno '\n' e il mio codice salta tutto fino a quel carattere.
Poi ogni | lo sovrascrive con uno '\0' e salva in arr il suo valore, quindi dentro ogni "arr" avrai i vari campi delimitati.
Ora devi scoprire se il buffer che leggi ha come ultimo carattere uno '\0', questo è fondamentale! se non hai tale carattere va tutto a rotoli, questo perch+ quando si lavora con le stringhe sul c è necessario che il vettore a carateri abbia uno '\0' finale.
Potresti inserirlo tu per sicurezza.
Ora modifica e torna a postare il nuovo codice

Purtroppo potrò provare solo in serata...

Il delimitatore effettivamente potrebbe esserci, ma non mi fido molto dell'output della seriale, ogni tanto fa le bizze, tipo scrivermi due volte di seguito lo stesso output anche se il codice non lo prevede.
In ogni caso un "a capo" tra l'header e il testo c'è sempre, quindi presumo lo veda così, provo a vedere se trovo qualcosa a livello di documentazione sui comandi AT.
Per quanto riguarda lo '\0' finale non c'è problema, posso prevederlo come standard nella stringa di comando, quella è definita da me a priori; solo un dubbio al riguardo: nell'sms devo indicarlo come \0 subito dopo l'ultimo parametro oppure deve essere racchiuso tra qualche carattere speciale?

Poi, nell'attesa della prova pratica vorrei fare un pò di chiarezza sulla teoria; se ho ben capito, una volta appurato che funziona tutto, il mio *arr dovrebbe essere così composto:
arr[0] = "comando"
arr[1] = "par1"
arr[2] = "par2"

ecc... E' corretto? Quindi alla mia funzione function(char** a) posso scriverla direttamente come function(arr)?

grazie

ciao

per sicurezza scriverei cosi:

....
  if (Sim900Serial.available())             
  {
    while(Sim900Serial.available())         
    {
      buffer[count++]=Sim900Serial.read();     
      if(count == 63)break;
    }
    buffer[count] = '\0';
    ...
    char* p = buffer;
    while ( *p && *p != '\n' && *p != '\r' ) ++p;
    while ( *p && *p == '\r' || *p == '\n ) ++p;

    while ( *p )
    {
        arr[i++] = p;
        while( *p && *p != '|' ) ++p;
        if ( !*p ) break;
        *p++ = '\0'
    }

il resto l'hai compreso benissimo, infatti puoi anche passare il vettore alla funzione, ma se ho letto bene è una variabile globale quindi non hai nemmeno bisogno di passarla.....

Diciamo che non è glbale ma sarà un pò tutto strutturato con i suoi header ecc, quindi devo vedere come organizzare la cosa...
Ok per il momento, appena possibile provo il codice e riferisco gli esiti..

PS: Dimenticavo una cosa, anche questo dubbio nasce dalle mie difficoltà nel comprendere bene l'aritmetica dei puntatori: visto che a function(char** a) devo passare la lista dei parametri e non il comando (questo mi serve a priori in un if/case per sapere quale function chiamare), a livello di passaggio di parametro come posso indicare il resto di arr a parte l'indice 0? Forse è meglio costruire un altro array e copiare arr? Una cosa del tipo

char** arr1 = arr+1;

Forse a livello di operatori ho scritto una st****a...

Grazie di nuovo intanto, gentilissimo...

Ciao

Nono, è facilissimo… per esempio:

char    *str = "0123456789" ;

printf("%s\n",(str+3)) ;

produrrà “3456789”

oppure

char    *str = "0123456789" ;
char    *ptr  ;
char     buf [20] ;

// facciamo finta che l'indirizzo di memora cui punta str sia 155

ptr = strstr(str,"4") ;

// ptr ora vale (155 + 4 ) perché ha trovato 4 in quarta posizione.

strncpy(buf,str,(ptr-str)) ;

// aggiungiamo il terminatore

buf[ptr-str+1] = '\0' ;

// eccetera...

Il trucco è che incremento, decremento e operazioni aritmetiche avvengono sempre a passi di sizeof(), in questo caso “char”. Puoi provare anche con strutture dati e cose del genere, funziona lo stesso.

Non ho scritto cosa succederebbe stampando buf, non ricordo mai anche dopo 20 anni che programmo se stamperebbe 0123 oppure 01234 ogni volta devo provare :smiley: :smiley: :smiley:

Discussione interessante.

Mmmmmhhh...quindi dovrei scrivere

arr1 = *arr+1

?

Vale69:
Mmmmmhhh...quindi dovrei scrivere

arr1 = *arr+1

?

se è dichiarato come

char *arr[20] ;

mi pare non faccia niente di "sano" e se anche il compilatore non protestasse non credo che otterresti l'effetto di spostarti nell'array. Sarebbe meglio non toccare "arr" ma al limite copiarne elementi e manipolare quelli. Per esempio potresti scrivere

    char   *arr[6] = { "uno" , "due" , "tre" , "quattro" , "cinque" , "sei" } ;

    char   *ptr = arr[0];  // punta a "uno"

    ptr = ( ptr + 1 ) ; // punta a "no"; oppure, lo stesso: ptr++
    printf("%s\n",ptr) ;
    printf("%s %s\n",*(arr+5),arr[5]) ; // stampa due volte "sei"

produce:

no
sei sei

arr1 = *arr+1 ;

probabilmente viene segnalato come errore (o almeno warning) se anche arr1 è un array o un puntatore
In quanto *arr legge il char puntato da arr e poi +1
Quindi, alla fine, leggi semplicemente un char che NON può essere assegnato ad un puntatore.
O se il gcc lo accettasse, sarebbe una operazione molto pericolosa.

Ti ricordo poi che:

char x=*(arr+1) ;
char x=*arr+1;

Sono diverse, il primo legge quello che viene puntato da arr+1 (quindi equivale a arr[ 1 ] )
mentre il secondo, come detto prima, legge il char puntato da arr e poi lo aumenta di uno

lo passi esattamente come lo dichiari:

void f(char* vbx[])
{
    byte i;
    for(i = 0; i < 2; ++i)
        if ( !strcmp("Like",vbx[i]) ) ....
}

void loop
{
    char* var[] = {"Arduino","Like"};
    f(var);
}

o come doppio puntatore

void f(char** vbx)
{
    byte i;
    for(i = 0; i < 2; ++i)
        if ( !strcmp("Like",vbx[i]) ) ....
}

Ragazzi, o ho un problema con la seriale, oppure non capisco che accidenti succede; di seguito il codice completo con i suggerimenti di vbextreme:

void loop()
{
  
  if (Sim900Serial.available())              
  {
    while(Sim900Serial.available())          
    {
      buffer[count++]=Sim900Serial.read();     
      if(count == 100)break;
  }


  char* p = buffer;
    while ( *p && *p != '\n' && *p != '\r' ) ++p;
    while ( *p && *p == '\r' || *p == '\n' ) ++p;

    while ( *p )
    {
        arr[i++] = p;
        while( *p && *p != '!' ) ++p;
        if ( !*p ) break;
        *p++ = '\0';
    }
delay(1000);

Al posto di | ho dovuto mettere ! perchè a quanto pare lo legge così, anche se non ho capito il motivo.
Di seguito l'output con questo sms (ne ho spediti 3 aspettando l'arrivo di ognuno):

comando|123|456|78|91|2

Output

comandoe 123 eZ� cocomando e123e Z� cocomandoe 123e Z�

A quanto apre a parte la prima parte poi scrive cose incomprensibili e neanche tutto il messaggio.

Suggerimenti??

Grazie