Shell per ESP 32
Table of Contents
Premessa
Scopo del codice disponibile al link e' quello di realizzare una shell per le board esp32 / arduino che metta ha disposizione i seguenti comandi di modo da rendere gestibile la board a runtime permettendo di utilizzare l'ambiente di shell nei seguenti modi :
- modo interattivo - i comandi possono essere digitati direttamente da linea di comando collegando la board tramite seriale;
- modo trasparente - i comandi di shell posso essere richiamati all'internoi del software con possibilita' che l'output di un comando venga trasferito in memoria.
Inoltre la shell mette a disposizione il comando macro che permette di eseguire file contenenti altri comandi di shell.
Introduzione
Il codice e' stato implementato utilizzando Visual Studio per Linux con l'estensione platformio e lo schma di test e' quello indicato nella figura di seguito in cui e' inserito un sensore di tipo DHT22 per la misura di temperatura ed umidita' ed un pulsante che pemette di accedere all'ambiente di shell. Inoltre sulla board e' in funzione un web server che rimane attivo anche quando l'ambiente di shell e' utilizzata in modo interattivo. Per una panoramica piu veloce si consiglia la visione del seguente video.
Figure 1: Schema di test
Classe Input
Questa classe permette la gestione dell'input in modo simile a quello di una qualsiasi shell inoltre si definisce un overload degli operatori di redirezione per rendere compatibile l'oggetto nativo di arduino di tipo String con quello del cpp di tipo string nonche' di effettuare concatenazione tra gli streaming acquisiti.
Metodi pubblici della classe
input(); // costruttore di default void setMode(boolean __set__) { __mode__ = __set__; } // imposta lo stato della classe void setEcho(boolean __set__) { __HideEcho__ = __set__; } // imposta il modo di echo dei caratteri void read_Rx(); // legge i caratteri da tastiera void rtrim(); // cacella i caratteri a dx della stringa con il match della stringa WHITESPACE void ltrim(); // cancella i caratteri a sx della stringa con il match della stringa WHITESPACE void trim(); // cancella i caratteri a sx e a dx della stringa con il match della stringa WHITESPACE int stoi() const; // converte la stringa in un intero void clear() // svuota il contenuto utile dell'oggetto {
Metodi privati della classe
/* utility per la gestione della classe */ std::string STR2str(String); // conversione da String a std::string String str2STR(std::string); // conversione da std::string a String boolean isMode() { return __mode__; } // ritorna lo stato della classe const String &getSTR() const { return __ROW__; } // conversione da std::string a String
Proprieta' della classe
boolean __mode__; // indica se il sistema si trova in modalita' input mode boolean __HideEcho__; // se true maschera i caratteri digitati std::string __row__; // riga digitata - oggetto di tipo string const std::string WHITESPACE = " \n\r\t\f\v"; // stringa di escape String __ROW__; // riga digitata - oggetto di tipo String int __SesTimeout__; // tempo di time out
Overloading
I seguenti overload permettono di utilizzare l'oggetto di tipo input come se fosse nativo del C++ e di assegnare oggetti di tipo String
nativo di ESP a oggetti di tipo string
del C++
// friend istream& operator>>(istream& is, input& __obj__){ __obj__.__row__=__obj__.STR2str();return is;} // sovracaricamento dell'operatore >> friend std::istream &operator>>(std::istream &is, input &__obj__) { __obj__.read_Rx(); return is; } // sovracaricamento dell'operatore >> friend std::ostream &operator<<(std::ostream &os, input &__obj__) { return os << __obj__.__row__; } // sovracaricamento dell'operatore << friend void operator>>(const std::string &lhs, input &__obj__) { __obj__.__row__ = lhs; } // sovracaricamento operator >> string -> input friend std::string &operator+=(std::string &lhs, const input &__obj__) { return lhs += __obj__.__row__; } // sovracaricamento operator string += input friend String &operator+=(String &LHS, const input &__obj__) { return LHS += __obj__.__ROW__; } // sovracaricamento operator String += input friend boolean operator==(const std::string &lhs, const input &__obj__) { return lhs == __obj__.__row__; } // sovracaricamento operator string==input friend boolean operator==(const input &__obj__, const std::string &rhs) { return rhs == __obj__.__row__; } // sovracaricamento operator input==string friend boolean operator==(const String &LHS, const input &__obj__) { return LHS == __obj__.__ROW__; } // sovracaricamento operator String==input friend boolean operator==(const input &__obj__, const String &RHS) { return RHS == __obj__.__ROW__; } // sovracaricamento operator input==String /* La dichiarazione operator string const &() const { return getstr(); } all'interno di una classe è un esempio di operatore di conversione personalizzato. Questo operatore consente di definire una conversione implicita da un oggetto di quella classe a un altro tipo specificato, in questo caso string (ovvero std::string). Vediamo in dettaglio cosa fa questa dichiarazione: operator string const &(): Questo dichiara un operatore di conversione. La keyword operator indica che stai definendo un operatore. string è il tipo di dati di destinazione al quale stai cercando di convertire l'oggetto. const & indica che il risultato della conversione è una costante riferita (riferimento costante) a un oggetto di tipo string. const: Questa è una parte della dichiarazione che indica che il riferimento restituito è costante, il che significa che non puoi modificare l'oggetto attraverso questo riferimento. &(): Questa parte della dichiarazione definisce l'operatore di conversione come una funzione senza argomenti, quindi la sintassi () indica che è una funzione. L'operatore restituirà un riferimento al tipo di dati specificato, in questo caso string. { return getstr(); }: Questo è il corpo dell'operatore di conversione. getstr() è un metodo della classe input che restituisce il contenuto della variabile privata __row__ come string. L'operatore di conversione utilizza questo metodo per ottenere il valore da convertire in string e quindi restituisce il riferimento a tale valore. In sintesi, l'operatore di conversione operator string const &() ti consente di convertire un oggetto della classe input in un oggetto di tipo string utilizzando una conversione implicita. Ad esempio, se hai un oggetto input chiamato myInput, puoi assegnarlo a una variabile string come se fosse già un oggetto di tipo string. Questo offre una maggiore comodità e flessibilità nell'uso degli oggetti della classe input. */ operator String const &() const { return getSTR(); } // permette assegmamenti del tipo String=input operator std::string const &() const { return getstr(); } // permette assegmamenti del tipo string=input // ------------------------------------------------------------------------------------------------------------------------------------------------------------------ input &operator=(const std::string &str) // overload operatore per assegnazioni del tipo input=string { this->clear(); __row__ = str; __ROW__ = str2STR(__row__); return *this; // Restituiamo l'oggetto corrente per supportare gli assegnamenti concatenati }
Acquisizione dalla seriale
il seguente medoto viene utilizzato quando si acquigisce uno stream da seriale ( uso della shell in modo interattivo ).
/* SerSrvMenu - Serial Communication, get characters from Serial (USB) Port ------------------------------------------------------------------------ La funzione permette permette di acquisire una stringa digitata da tastiera. I caratteri digitati vengono memorizzati nell'array di carartteri receivedchars[] terminato dal carattere '\0' quando si preme il gtasto return. Per evitare lo sconfinamento dei limiti dell'array ad ogni pressione del tasto si incremnta l'indice dell'array che quando raggiunge il limite massimo viene decrementato di uno di modo che nell'ultima posizione ci sia sempre l'ultimo carattere digitato che alla pressione del tasto return viene sostituito dal carattere di escape '\0'. Inoltre vi è un controlllo della durata del tempo di acquisiszione con il contatore che viene resettato ad ogni pressione di tasto. Infine l'impostazione a true della variabile HideEcho implica che il carattere digitato viene visualizzato con il carattere *. Ser_Rx() | +---[controllo del timeout] | +---[blocco di lettura]{ | +----[blocco di acquisizione] */ void input::read_Rx() { unsigned long SessStart = millis(); // durata del timeout char endMarker1 = '\r'; // Some terminal client apps send a CR char endMarker2 = '\n'; // Others just a LF character, therefore we need to check for both char rc; // carattere digitato const byte numChars = MaxSerRx; boolean newData = false; static byte ndx = 0; // inizializza l'indice dell'array dei caratteri digitati utilizzato nella funzione read_RX char receivedChars[numChars]; // an array to store the received data // Serial.println(numChars); if (__row__.length() > 0) { ndx = __row__.length(); for (int iAux = 0; iAux <= ndx; iAux++) receivedChars[iAux] = __row__[iAux]; } while (newData == false) { // blocco di lettura // controllo della durata della sessione di lettura // if (millis() - SessStart > __SesTimeout__) { // blocco di controllo // Serial.print("The session has timed out ("); // Serial.print(__SesTimeout__ / 1000); // Serial.println(" seconds)..."); // break; // } // end of control block std::cout << std::flush; /* Ciclo di acquisizione della stringa. Si esegue fino a quando non si preme return. Ad ogni pressione del tasto si resetta il contatore del timeout. Alla pressione del tasto return */ while (Serial.available()) { // blocco di acquisizione dei caratteri SessStart = millis(); rc = Serial.read(); if (rc == endMarker1 || rc == endMarker2) { // if a CR or LF was received receivedChars[ndx] = '\0'; // terminate the character array (string)... ndx = 0; newData = true; // char temp = Serial.read(); goto ReturnReceivedString; // return everything } else if (rc == 127) { // A DEL character (decimal 127) was received ndx = ndx - 1; // Set the Array-Pointer back to delete the last characte Serial.print( rc); // Echo del DEL character back that the Terminal Client app } // removes the last character from the screen else { receivedChars[ndx] = rc; // Receive normal characters.. ndx++; // Serial.print(ndx); if (ndx >= numChars) { ndx = numChars - 1; } if (__HideEcho__ == false) { // Hide echo if user types in a password... Serial.print(rc); } else { // Normal echo Serial.print("*"); } } // } // fine del blocco di acquisizione dei caratteri } // fine del blocco di lettura ReturnReceivedString: // copia l'array di input nell'oggetto STRING __ROW__.clear(); for (int iAux = 0; receivedChars[iAux] != '\0'; iAux++) __ROW__ += receivedChars[iAux]; // coversione dell'oggetto String in string __row__.clear(); __row__ = input::STR2str(__ROW__); } // fine della funzione Ser_Rx()