Leggere ed interpretare una Stinga

Ciao a tutti,
è qualche anno che uso Arduino per hobby ma da qualche giorno mi sono messo a giocare con la seriale: inviare comandi via IDE ad esempio, oppure far dialogare 2 arduino MEGA tramite la seriale interna numero 2 tra di loro per scambiarsi informazioni.
Vorrei costruire uno sketch in cui leggo la seriale dalla IDE come Stringa, e poi far delle cose in base a ciò che è scritto. Le funzioni per trovare testo in una stringa che conosco sono ad esempio equalsIgnoreCase, indexOf ...
La domanda è: se scrivo "accendi led 1 per 10 secondi" c è un modo relativamente semplice per associare ad una variabile byte numero_led il valore 1 e ad un'altra variabile int tempo_accensione il valore di 10mila millisecondi?

Usando indexOf posso capire che nel comando c è scritto "accendi led" piuttosto che "spegni led", il problema è che poi dovrei spostare il cursore di 1 posto, leggere come INT il valore che c è dopo (1), poi spostare il cursore di altri 5 posti , capire che 10 è formato da 2 caratteri (1 e 0) e fargli acquisire 1 e 0 in una sola int che sarebbe = 10. Non so se rendo l' idea.
Non chiedo lo sketch già fatto, ma soltanto per favore qualche dritta su comandi, funzioni...compatibili con arduino! Grazie a tutti

Primo errore, usare la classe String invece che le stringhe classiche del 'C' (cosa che, sulle classiche MCU AVR, prima o poi crea sicuramente problemi), secondo non aver studiato la AVR libc dove, in string.h ci sono tutte le funzioni per manipoolare le stringhe del 'C', separare in "token", ecc. e la stdlib.h dove trovi quelle per convertire stringhe in numeri, ecc.

Buon studio! :slight_smile:

Guglielmo

Qui trovi una demo della libreria SerialCmd di Guglielmo.
L'ho scritta tempo addietro per l'utente giggetto, vedi se può esserti utile.

Ciao.

... beh, visto che la citi, allora diamo anche il link alla discussione relativa all libreria, il link a GitHub e segnaliamo che si installa dal "Library Manager" dell'IDE :wink:

Guglielmo

Grazie!
avevo già letto la pagina relativa a string.h
https://www.nongnu.org/avr-libc/user-manual/group__avr__string.html
ma siccome sono un hobbista, non riesco a capire cosa fa ogni funzione dato che non ci sono esempi. Farò così, con un po di tempo cercherò in giro piccoli sketch con esempi di ogni funzione, per capire come combinarle per arrivare allo scopo.

Anche io sono un hobbista, ma un tempo arduino e il C++ non esistevano e non c'era alternativa e si dovevano usare le librerie standard C.

Comunque è vero senza una guida, un esempio ecc si fatica tanto.
Ma noi siamo qui, quando hai dubbi su una funzione chiedi.

Ciao.

... ma li hai cercati ? ? ? :open_mouth: :open_mouth: :open_mouth:

Perché le funzioni di string.h (ma anche di stdlib.h) NON sono solo per Arduino, ma generali della libreria del 'C' e ... internet è pieno zeppo di esempi di uso di quelle funzioni ... :roll_eyes:

Guglielmo

Si, mi sto documentando su altri siti con degli esempi per ogni funzione e cerco quella che fa al caso mio.
Purtroppo, anche se String può causare problemi con la Ram (come ho letto in un altro Topic su questo stesso forum) spesso ce le dobbiamo tenere perché vuoi o non vuoi semplificano molto.
Così a occhio non ho trovato IndexOf per array di caratteri, o altre comodità. Inoltre per me che lavoro ormai più con ESP32 piuttosto che con Arduino, tutto ciò che arriva da Telegram è visto come Stringa. Quindi per pulire la coscienza l unica cosa che posso fare è convertire la Stringa passata da telegram in array di caratteri e lavorare su quelli..anche se la Stringa rimane, e ad ogni messaggio ricevuto (o da inviare) la Libreria vuole sempre Stringa.
Quindi a questo punto mi chiedo: dato che sono obbligato dalla libreria ad usare Stringa quanto meno per ricevere il messaggio di testo, se poi la lavoro usando funzioni o comandi, rischio di caricare ancora di più la ram? oppure visto che il danno è fatto, tanto vale usarli?

Forse non visto il link al post #3 lo rimetto qui:

La libreria e la demo assieme fanno tutto quello che chiedi, puoi indagare su come è fatto e se lo trovi più semplice lo realizzi con la classe String. In effetti su ESP32 c'è molta più ram e le probabilità che si frammenti la memoria sono minori ma esistono. Se l'applicazione fallisce una volta al mese e non è un problema, vai pure con String ecc.

Ciao.

Grazie, mi era sfuggito. Lo sto guardando e a prima vista non ci capisco molto:
nel loop c è solo

   int8_t err = mySerCmd.ReadSer();
   if (!err) {
      Serial.print("Comando sconosciuto.\n");
   }

dove si trova la parte di script che legge da seriale ( serial.available() ) ? dato che è inclusa anche la servo.h mi viene difficile capire come adattare al mio scopo. A breve scaricherò la libreria e farò delle prove, grazie delle dritte! nel frattempo cercherò anche di elaborare uno script che mi legge dalla seriale e se trova quello che si aspetta, fa delle cose. Se gradite, se ci riesco posto lo script così vi fate quattro risate

Si trova dentro la libreria.

La servo è incluso poiche giggetto voleva un esempio che la usasse o forse lui avrebbe usato dei motori passo passo, ma in wokwi al tempo il passo passo non c'èra è comunque è una demo che mostra come fare eseguire del codice in base a ciò l'utente scrive nella riga di comando.

Quindi es, scrivi: srv, 180
e il servo va nella posizione 180
scrivi: srv,90
e il servo va 90

Ovviamente qualunque valore nel range 0÷180 è ammesso.

Ciao.

Dimentica la seriale, è quello lo scopo della libreria, di NON fati vedere la parte complessa e di chiamare delle specifiche "funzioni" per ogni comando ricevuto dando la possibilità di recuperare i vari parametri separati da un "separatore" prefissato.

Però, se non ti studi il readme che accompagna la libreria ... NON avrrivi da nessuna parte.

Guglielmo

Eccomi, ho provato e condivido questo stupendo sketch con la community. Sono ironico.

PREMESSA: inviare via seriale un comando necessariamente UGUALE a:
accendi LED x minuti y
Dove X e Y possono essere a 1 o 2 cifre.

RAGIONAMENTO (ahahah, di sabato e per hobby):

  1. leggo dalla seriale IDE come Stringa
  2. calcolo la lunghezza della Stringa
  3. creo un array di caratteri della dimensione della lunghezza Stringa +1 (dato che a fine stringa mette Zero, o qualche carattere speciale, non so di preciso.. ottenuto +1 sperimentalmente)
  4. inserisco il contenuto della Stringa all' interno dell' array di caratteri appena creato
  5. tramite indexOf leggo al Stringa originale e vedo se c è la parola "accendi LED"
  6. se c è, scansiono con un For tutto l' array di caratteri alla ricerca del separatore, che nel mio caso è il carattere vuoto " " (barra spazio, per gli amici ASCII 32)
  7. mi annoto in un array di byte la posizione (cella numero) del Separatore trovato, fino a fine FOR. così avrò un array con dentro i numeri delle posizioni degli SPAZI individuati dall' array di caratteri
  8. creo un Token1 di dimensione pari alla differenza tra le posizioni (celle) dei Separatori individuati nell'array del punto 7. questo Token1 è di tipo char[x]
  9. riempio token1 con i numeri compresi tra 1 separatore e l altro
  10. tramite atoi() converto token1 che è un array di caratteri, nel mio caso per forza numeri, in "int" così so QUALE led accendere
  11. stessa cosa con token2 mi calcolo per quanti secondi (o minuti, insomma poi si vedrà) accendere il led.
    Ciò premesso ecco lo sketch:
void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
}

String leggi_da_seriale_Stringa; //che è una Stringa

int k=0; //così...
int lunghezza_della_Stringa=0;

int array_posizione_dei_separatori[10];
int contatore_array_dei_separatori=0;
char carattere_separatore=' ';
byte numero_led_da_accendere=0;
unsigned long tempo_led_acceso=0;

void loop() {
  // put your main code here, to run repeatedly:

  if(Serial.available())
  {

          leggi_da_seriale_Stringa = Serial.readString(); //leggo dalla seriale come Stringa
          lunghezza_della_Stringa = leggi_da_seriale_Stringa.length(); //calcolo la dimensione della stringa

          char array_di_caratteri[lunghezza_della_Stringa+1]; //li dichiaro qui e non nell header perché prima non potrei sapere quanto è lunga la Stringa ricevuta via Seriale!
                   
          Serial.println("ho ricevuto " +(String)leggi_da_seriale_Stringa);
          Serial.println("lunghezza_della_Stringa = a " +(String)lunghezza_della_Stringa);
        
         
          leggi_da_seriale_Stringa.toCharArray(array_di_caratteri, lunghezza_della_Stringa+1); //carico nell array di caratteri la Stringa ricevuta via seriale, quindi avrò 1 carattere in ogni cella

         if(leggi_da_seriale_Stringa.indexOf("accendi LED") != -1)
          {
            Serial.println("hai inviato un comando di accensione LED");
            contatore_array_dei_separatori=0;
            
            for(k=0;k<lunghezza_della_Stringa+1;k++)
            {
              if(array_di_caratteri[k]==carattere_separatore) //se trovi 1 carattere separatore, inserisci la sua posizione in un array che prende nota delle coordinate di ogni separatore, fino a 1 massimo di 10 sep
              {
                Serial.println("Trovato separatore!");
                array_posizione_dei_separatori[contatore_array_dei_separatori]=k;
                contatore_array_dei_separatori++;
              }
            }
            
            Serial.println("ecco l array_posizione_dei_separatori:"); //mostra l array coi separatori,ogni numero è la posizione all interno dell array del separatore nell array originale
            for(k=0;k<10;k++)
            {
              Serial.print(" " +(String)array_posizione_dei_separatori[k]);
            }
            Serial.println("");

            byte dimensione_token1=(array_posizione_dei_separatori[2]-array_posizione_dei_separatori[1]); //mi invento 1 array token nel quale verso byte-a-byte il contenuto tra un separatore e l altro
            char token1[dimensione_token1];
            for(k=0;k<dimensione_token1;k++)
            {
              token1[k]=array_di_caratteri[(array_posizione_dei_separatori[1]+1+k)]; //metto +1 xke deve iniziare dal carattere successivo al separatore stesso
            }

            Serial.print("token1 è ");
            for(k=0;k<dimensione_token1;k++)
            {
              Serial.print(token1[k]);
            }
            Serial.println("");
            numero_led_da_accendere=atoi(token1);
            Serial.print("numero_led_da_accendere = ");
            Serial.println(numero_led_da_accendere);

          
            byte dimensione_token2=((lunghezza_della_Stringa)-array_posizione_dei_separatori[3]); //il secondo array non ha lo spazio come finale, ma il fine-array.
            char token2[dimensione_token2];
            for(k=0;k<dimensione_token2;k++)
            {
              token2[k]=array_di_caratteri[(array_posizione_dei_separatori[3]+1+k)]; //metto +1 xke deve iniziare dal carattere successivo al separatore stesso
            }

            Serial.print("token2 è ");
            for(k=0;k<dimensione_token2;k++)
            {
              Serial.print(token2[k]);
            }
            Serial.println("");            
            tempo_led_acceso=atoi(token2);
            Serial.print("tempo_led_acceso in secondi = ");
            Serial.println(tempo_led_acceso); 
           
          }

          
  }

delay(100);
}

Ora, so benissimo che si poteva dividere con strtok, il problema è che non ho capito come!
Leggendo in giro e guardando questo video : https://www.youtube.com/watch?v=ti3AVAOoszM
vedo che è molto facile ma non saprei oltre a "stampare sulla seriale" come salvare su variabili esterne il Numero_Led ed il Tempo_di_accensione dato che il While prosegue e non saprei come "agganciare" i dati che mi interessano.

ora stacco, domani mi dedicherò alla Fotografia e di conseguenza lunedì rileggendo questo mio sketch sicuramente non ci capirò più nulla, vi prego di compatirmi. E' probabile che settimana prossima proverò con strtok a "beccare" i valori che mi interessano intercettare

Quale while?
C'è un if (Serial.available()) { e dentro la readString(), nella variabile leggi_da_seriale_Stringa c'è il comando che hai inserito.

Se questa variabile è "NON vuota" vuole dire che c'è un comando di cui effettuare il parsing. Durante il parsing non deve leggere altro da seriale.

Prima di questo devi trovare il modo di collezionare la stringa con readString(), all'uscita.

Ciao.

Il while di cui parlo è quello del video. Lunedì mi metto a studiare per bene la libreria tua, per vedere se mi semplifica le cose (sicuramente si)

Scusa non avevo visto il video, in genere non guardo video per queste cose, preferisco il testo. Si ok dal while di esce quando token == NULL o nullptr. Se prendi un stringa e ne stampi i codici ascii vedi che ad esempio ABC è composta da 4 elementi seguenti: 65, 66, 67, 0.
Lo zero finale indica la fine della collezione dei caratteri ed è essenziale per essere conforme alle C string. Ma la C string potrebbe pure essere come segue: 65, 66, 67, 0, 67, 66, 65, 0. Sono otto elementi di array il puntatore di cui parla aliverti punta prima a 65 (indice 0), poi punta a 67 (indice 4), in entrambe i casi il terminatore 0 è presente.

Se vuoi approfondire qui qualcosa su i vettori e C string.

Mentre qui come stampare il contenuto delle celle di ram ecc.

Ciao.

Direi di no

La stringa di C termina alllo zero binario, il primo zero binario
È la definizione di stringa di C

Se poi anche lo array di char fosse più grande e magari anche popolato la cosa sarebbe comunque ignorata da tutte le funzioni di stringa (stringa di C intendo)

Infatti la strtok chiede "due" parametri: la stringa da analizzare e la stringa (non il carattere) che funge da separatore, ed ambedue terminate da uno zero binario

Ti potrà sembrare strano ma non credo si tratti di questo, sicuramente mi sono espresso di fretta e compreso male.

In breve, quando strtok (o altro) incontra il separatore al suo posto ci mette lo zero di terminazione e restituisce il puntatore a indice 0.
Quando la si richiama strtok parte dal indice seguente lo 0, quindi da 67 e restituisce il puntatore a questo.

Quando strtok non ha più nulla da fare la C string contiene quei codici.

Tutto qui.


Cioè no
È l'array di caratteri che contiene gli zeri binari

non puoi usarlo come stringa unica

Devi esserti appuntato tutti i successivi puntatori a caratteri che la strtok restituisce ad ogni successiva sua chiamata

Puntatori che usi come singole stringhe

Certo che non si può, tutte le funzioni standard si aspettano il terminatore '\0'.

strtok mette lo zero e si salva il puntatore al successivo elemento e si ferma quando incontra lo zero finale della C string e restiuisce NULL. Era solo per mostrare in linea di massima come lavora, avrei dovuto scrivere un esempio di codice e poi stampare l'array completo e mostrarlo.

Ciao.