Salvare log.txt su google drive con ESP32C6

Sto facendo vari test per cercare di salvare un file .txt e/o aggiornarne il contenuto usando un ESP32C6.

Non riesco però a far funzionare lo script. Ma andiamo per punti:

Su google app script ho creato e reso eseguibile il seguente script:

function doPost(e) {
  try {
  if (e.parameter.data) {
    return ContentService.createTextOutput("Ricevuto: " + e.parameter.data);
  }
  // Folder and file details
  var folderName = "ESP32_Logs";
  var fileName = "log.txt";
  // Find or create folder
  var folders = DriveApp.getFoldersByName(folderName);
  var folder = folders.hasNext() ? folders.next() : DriveApp.createFolder(folderName);

  // Find or create file
  var files = folder.getFilesByName(fileName);
  var file = files.hasNext() ? files.next() : folder.createFile(fileName, "");

  // Check for data parameter
  if (!e.parameter.data) {
    return ContentService.createTextOutput("Error: 'data' parameter is missing.");
  }
  // Append data to the file (handle potential JSON data)
  var data = e.parameter.data;
  try {
    var jsonData = JSON.parse(data); // Try parsing as JSON
    data = JSON.stringify(jsonData, null, 2); // Pretty-print for readability
  } catch (err) {
    // Not valid JSON, keep as plain text
  }
  file.appendText(data);
  return ContentService.createTextOutput("Log saved successfully.");
} catch (error) {
  return ContentService.createTextOutput("Error: " + error.toString());
}
}

Contemporaneamente ho usato il seguente codice sulla ESP32:

void sendLogData(const String& logData) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    WiFiClientSecure client;
    client.setInsecure(); // Disabilita la verifica del certificato
    
    String currentUrl = serverName; // URL iniziale
    int maxRedirects = 5;          // Numero massimo di redirect consentiti
    int redirectCount = 0;

    while (redirectCount < maxRedirects) {
      Serial.println("Connecting to: " + currentUrl);
      http.begin(client, currentUrl);
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");

      String postData = "data=" + logData;
      int httpResponseCode = http.POST(postData);

      if (httpResponseCode > 0) {
        String response = http.getString();

        if (httpResponseCode >= 300 && httpResponseCode < 400) {
          // Cerca il tag <a href> per ottenere il nuovo URL
          int hrefStart = response.indexOf("<A HREF=\"");
          if (hrefStart != -1) {
            hrefStart += 9; // Salta "<A HREF=\""
            int hrefEnd = response.indexOf("\"", hrefStart);
            if (hrefEnd != -1) {
              String newUrl = response.substring(hrefStart, hrefEnd);
              Serial.println("Redirecting to: " + newUrl);

              // Controlla se l'URL è relativo o assoluto
              if (newUrl.startsWith("/")) {
                // Combina l'URL relativo con il dominio originale
                int protocolEnd = currentUrl.indexOf("//") + 2;
                int pathStart = currentUrl.indexOf("/", protocolEnd);
                if (pathStart == -1) pathStart = currentUrl.length();
                newUrl = currentUrl.substring(0, pathStart) + newUrl;
              }
              currentUrl = newUrl;
              redirectCount++;
              http.end(); // Chiude la connessione prima di reindirizzare
              continue;   // Procede al prossimo loop
            }
          }
          Serial.println("Redirect URL not found in <A HREF>");
          break; // Interrompi se non riesci a trovare il redirect
        } else {
          // Risposta valida
          Serial.println("Response: " + response);
          break; // Esci dal ciclo su successo
        }
      } else {
        // Errore HTTP
        Serial.println("Error: " + String(httpResponseCode));
        break;
      }
      http.end(); // Chiude la connessione dopo ogni iterazione
    }

    if (redirectCount >= maxRedirects) {
      Serial.println("Too many redirects!");
    }
  } else {
    Serial.println("WiFi not connected");
  }
}

Il problema che non riesco a capire è che se il file e la cartella non esistono vengono creati ma il file è vuoto. Se invece il file esiste in serial print mi ritrovo:

5:29:48.215 -> Connecting to: https://script.google.com/macros/s/XXXXXXXXXXXXXX/exec
15:29:50.424 -> Redirecting to: https://script.googleusercontent.com/macros/echo?user_content_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWckm76-hGir53bGrc602iLx2va1MhS-rt0212NZu7tmNo-CGKJJXzB5jRm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnEtCOQ2ISU2aAbVqEpmvfml3MPAGL4Fz83fsHzP4Oceo5EzePk_rqhpSrpR5JWlbqdx58zHd7cVQnwZuu1UjrY8rglz6y9OqyA&amp;lib=MG7DD04kIxA_rxQD0tnOzuYFIwK-8SiuQ
15:29:50.457 -> Connecting to: https://script.googleusercontent.com/macros/echo?user_content_key=XXXXXXXXXXXXckm76-hGir53bGrc602iLx2va1MhS-rt0212NZu7tmNo-CGKJJXzB5jRm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnEtCOQ2ISU2aAbVqEpmvfml3MPAGL4Fz83fsHzP4Oceo5EzePk_rqhpSrpR5JWlbqdx58zHd7cVQnwZuu1UjrY8rglz6y9OqyA&amp;lib=MG7DD04kIxA_rxQD0tnOzuYFIwK-8SiuQ
15:29:51.145 -> Response: <!DOCTYPE html><html lang="it"><head><meta name="description" content="Elaborazione di testi, presentazioni e fogli di lavoro sul Web"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"><link rel="shortcut icon" href="//docs.google.com/favicon.ico"><title>Pagina non trovata</title><meta name="referrer" content="origin"><link href="//fonts.googleapis.com/css?family=Product+Sans" rel="stylesheet" type="text/css" nonce="wrIhtRWWO1osi8skGK7s6A"><style nonce="wrIhtRWWO1osi8skGK7s6A">.goog-inline-block{position:relative;display:-moz-inline-box;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}#drive-logo{margin:18px 0;position:absolute;white-space:nowrap}.docs-drivelogo-img{background-image:url(//ssl.gstatic.com/images/branding/googlelogo/1x/googlelogo_color_116x41dp.png);-webkit-background-size:116px 41px;background-size:116px 41px;display:inline-block;height:41px;vertical-align:bottom;width:116px}.docs-drivelogo-text{color:#000;display:inline-block;opacity:.54;text-decoration:none;font-family:"Product Sans",Arial,Helvetica,sans-serif;font-size:32px;text-rendering:optimizeLegibility;position:relative;top:-6px;left:-7px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:144dpi){.docs-drivelogo-img{background-image:url(//ssl.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_116x41dp.png)}}sentinel{}</style><style type="text/css" nonce="wrIhtRWWO1osi8skGK7s6A">body {background-color: #fff; font-family: Arial,sans-serif; font-size: 13px; margin: 0; padding: 0;}a, a:link, a:visited {color: #112ABB;}</style><style type="text/css" nonce="wrIhtRWWO1osi8skGK7s6A">.errorMessage {font-size: 12pt; font-weight: bold; line-height: 150%;}</style></head><body><div id="outerContainer"><div id="innerContainer"><div style="position: absolute; top: -80px;"><div id="drive-logo"><a href="/"><span class="docs-drivelogo-img" title="Logo Google"></span><span class="docs-drivelogo-text">&nbsp;Drive</span></a></div></div><div align="center"><p class="errorMessage" style="padding-top: 50px">Impossibile aprire il file in questo momento.</p><p> Verifica l'indirizzo e riprova.</p><div style="background: #F0F6FF; border: 1px solid black; margin-top: 35px; padding: 10px 125px; width: 300px;"><p><strong>Aumenta la tua produttività con Google Drive</strong></p><p>Le app in Google Drive rendono più facile creare, salvare e condividere online documenti, fogli di lavoro, presentazioni e altro ancora.</p><p>Per ulteriori informazioni, consulta <a href="https://drive.google.com/start/apps">drive.google.com/start/apps</a>.</p></div></div></div></div></body><style nonce="wrIhtRWWO1osi8skGK7s6A">html {height: 100%; overflow: auto;}body {height: 100%; overflow: auto;}#outerContainer {margin: auto; max-width: 750px;}#innerContainer {margin-bottom: 20px; margin-left: 40px; margin-right: 40px; margin-top: 80px; position: relative;}</style></html>

Ovviamente ho controllato che cartella e file txt in drive abbiano i permessi per essere editati da chiunque anche se teoricamente non dovrebbe essere necessario dato che lo script viene eseguito come se fossi io quindi con pieni poteri.

Non è chiaro se lo script lo hai scritto tu o se lo hai preso da qualche parte online, però ci sono molte cose che non vanno.

In questo pezzo di codice, quando la tua richiesta POST contiene il parametro data lo script risponde con "Ricevuto + " e poi si chiude non andando a salvare nulla nel file (istruzione return)
Questo significa che il file viene creato solo nel caso di richiesta malformata (ad esempio con il browser) che non contiene il parametro data.

Qui invece lo script si aspetta un contenuto di tipo JSON (e non è questo il tuo caso).
Se data non è un JSON viene sollevato un errore anche se poi non viene fatto nulla e dovrebbe proseguire con il resto, ma rimane comunque un pezzo di codice "estraneo"

Infine questa istruzione è proprio sbagliata perché appendText() non è un metodo della classe File

Ho modificato al volo il tuo script per farlo funzionare, anche se l'ho provato solo con Postman (fantastica app che ti consiglio di scaricare se vuoi sviluppare questo genere di cose).
Ci sono altre cose che si possono migliorare, ma almeno può essere una base di partenza funzionante.

Per quanto riguarda la risposta ottenuta con l'ESP32, sei sicuro di aver fatto il deploy dello script impostando "chiunque" come utenti autorizzati?

1 Like

Sono partito da progetti di terzi che ho trovato online e ho cercato di ispirarmi evidentemente senza troppo successo.

Ho modificato lo script:

function doPost(e) {
  try {
    // Folder and file details
    var folderName = "ESP32_Logs";
    var fileName = "log.txt";
    
    // Find or create folder
    var folders = DriveApp.getFoldersByName(folderName);
    var folder = folders.hasNext() ? folders.next() : DriveApp.createFolder(folderName);

    // Find or create file
    var files = folder.getFilesByName(fileName);
    var file = files.hasNext() ? files.next() : folder.createFile(fileName, "");

    // Check for data parameter
    if (!e.parameter.data) {
      return ContentService.createTextOutput("Error: 'data' parameter is missing.");
    }    
    var data = e.parameter.data;

    // Get actual file content
    var text = file.getBlob().getDataAsString();

    // Append data to a new line 
    text += '\n' + data;

    // Save file content
    file.setContent(text);
    
    return ContentService.createTextOutput("Log saved successfully.");
  } catch (error) {
    return ContentService.createTextOutput("Error: " + error.toString());
  }
}

Ma ciò nonostante l'ESP32 mi risponde:

12:32:49.261 -> Connecting to: https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxpH49JWQwgo3e1BqKLbaQQESv3B_zRyIlnXPemkcHicK4qp/exec
12:32:52.566 -> Redirecting to: https://script.googleusercontent.com/macros/echo?user_content_key=xxxxxxxxxxxxxxxxxxxxxxxxxxHyUkDz6Cvl13NVRwMmtDtYtxdvZg8bSKq6n8SyOuEQ3RZEou95jhDIq8nJKONndm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnEYi9axUjhgtB4nDp7htJv7lmbuf-FwmeZQx1DhjEuHhSrktuhOA3z3KUnin9_fHyMtVL-Jmqvx5iTcb2bvXRuY8W646K3Opow&amp;lib=MG7DD04kIxA_rxQD0tnOzuYFIwK-8SiuQ
12:32:52.598 -> Connecting to: https://script.googleusercontent.com/macros/echo?user_content_key=xxxxxxxxxxxxxxxxxxxxxDz6Cvl13NVRwMmtDtYtxdvZg8bSKq6n8SyOuEQ3RZEou95jhDIq8nJKONndm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnEYi9axUjhgtB4nDp7htJv7lmbuf-FwmeZQx1DhjEuHhSrktuhOA3z3KUnin9_fHyMtVL-Jmqvx5iTcb2bvXRuY8W646K3Opow&amp;lib=MG7DD04kIxA_rxQD0tnOzuYFIwK-8SiuQ
12:32:53.276 -> Response: <!DOCTYPE html><html lang="it"><head><meta name="description" content="Elaborazione di testi, presentazioni e fogli di lavoro sul Web"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"><link rel="shortcut icon" href="//docs.google.com/favicon.ico"><title>Pagina non trovata</title><meta name="referrer" content="origin"><link href="//fonts.googleapis.com/css?family=Product+Sans" rel="stylesheet" type="text/css" nonce="PTQGHNGl4Oo9k0mSN4v-lQ"><style nonce="PTQGHNGl4Oo9k0mSN4v-lQ">.goog-inline-block{position:relative;display:-moz-inline-box;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}#drive-logo{margin:18px 0;position:absolute;white-space:nowrap}.docs-drivelogo-img{background-image:url(//ssl.gstatic.com/images/branding/googlelogo/1x/googlelogo_color_116x41dp.png);-webkit-background-size:116px 41px;background-size:116px 41px;display:inline-block;height:41px;vertical-align:bottom;width:116px}.docs-drivelogo-text{color:#000;display:inline-block;opacity:.54;text-decoration:none;font-family:"Product Sans",Arial,Helvetica,sans-serif;font-size:32px;text-rendering:optimizeLegibility;position:relative;top:-6px;left:-7px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:144dpi){.docs-drivelogo-img{background-image:url(//ssl.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_116x41dp.png)}}sentinel{}</style><style type="text/css" nonce="PTQGHNGl4Oo9k0mSN4v-lQ">body {background-color: #fff; font-family: Arial,sans-serif; font-size: 13px; margin: 0; padding: 0;}a, a:link, a:visited {color: #112ABB;}</style><style type="text/css" nonce="PTQGHNGl4Oo9k0mSN4v-lQ">.errorMessage {font-size: 12pt; font-weight: bold; line-height: 150%;}</style></head><body><div id="outerContainer"><div id="innerContainer"><div style="position: absolute; top: -80px;"><div id="drive-logo"><a href="/"><span class="docs-drivelogo-img" title="Logo Google"></span><span class="docs-drivelogo-text">&nbsp;Drive</span></a></div></div><div align="center"><p class="errorMessage" style="padding-top: 50px">Impossibile aprire il file in questo momento.</p><p> Verifica l'indirizzo e riprova.</p><div style="background: #F0F6FF; border: 1px solid black; margin-top: 35px; padding: 10px 125px; width: 300px;"><p><strong>Aumenta la tua produttività con Google Drive</strong></p><p>Le app in Google Drive rendono più facile creare, salvare e condividere online documenti, fogli di lavoro, presentazioni e altro ancora.</p><p>Per ulteriori informazioni, consulta <a href="https://drive.google.com/start/apps">drive.google.com/start/apps</a>.</p></div></div></div></div></body><style nonce="PTQGHNGl4Oo9k0mSN4v-lQ">html {height: 100%; overflow: auto;}body {height: 100%; overflow: auto;}#outerContainer {margin: auto; max-width: 750px;}#innerContainer {margin-bottom: 20px; margin-left: 40px; margin-right: 40px; margin-top: 80px; position: relative;}</style></html>
12:32:53.634 -> WiFi spento completamente

Ho provato anche da prompt con curl ma il risultato rimane lo stesso. Il deployment per sicurezza l'ho rifatto da zero con esegui come "me" e accessibilità "chiunque".

EDIT: per la serie le stranezze e dove incontrarle nonostante nel seriale dia quella risposta di errore. Invece adesso i dati vengono caricati e salvati correttamente. Bizzarro che dia quella strana risposta ma comunque almeno ho una base di partenza.

Quindi lo script funziona adesso ed è già un passo avanti.

Per quanto riguarda lo sketch (che prima avevo guardato poco) vedo che viene gestito in modo "manuale" il redirect della pagina.

Forse l'esempio di partenza è un po' datato perché la libreria HTTPClient supporta il redirect già di suo, basta abilitarlo e puoi rimuovere tutta la logica di estrazione dell'url di redirect e di tentativi di connessione.

http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);

La risposta che ottieni dal server è una risposta "generica" di errore, probabilmente la richiesta HTTP non è del tutto "conforme" a quanto richiesto, ma comunque basta per eseguire correttamente lo script o qualcosa di simile.

Forse ti conviene fare una richiesta di tipo GET che tipicamente è un po' più facile da gestire in quanto sta tutto nell'url e non devi costruire il body specificandone il Content-Type.
In tal caso ti basta modificare lo script chiamando la funzione doGet() invece di doPost() e ovviamente adattare anche lo sketch dell'ESP32

1 Like

Purtroppo il GET mi torna male in quanto il contenuto della variabile di log sono svariate righe con "a capo" e punteggiatura varia quindi quando va a comporre l'url mi crea una moltitudine infinita di errori.

Per farti capire meglio questo è quello che trasmette in un colpo solo:

WiFi connesso
Indirizzo IP: 192.168.1.42

OTA avviato
Starting TelegramBot...
Manca meno di zero secondi. Dormo fino alle 08 del giorno successivo: 82920 secondi
Time to sleep: 82920 seconds

Test Telegram connection... OK
Configuro ESP32 per dormire 82920 secondi
Inizio a dormire

In ogni caso seguendo i tuoi consigli ho modificato lo sketch così:

void sendLogData(const String& logData) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    WiFiClientSecure client;
    client.setInsecure();  // Disabilita la verifica del certificato

    String currentUrl = serverName;  // URL iniziale

    Serial.println("Connecting to: " + currentUrl);
    http.begin(client, currentUrl);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");

    String postData = "data=" + logData;
    int httpResponseCode = http.POST(postData);

    http.end();  // Chiude la connessione dopo ogni iterazione

  } else {
    Serial.println("WiFi not connected");
  }
}

E funziona. Peccato per il GET perché mi sarebbe piaciuto approfondire ma sarà per un'altra volta.

Si il GET ha il problema che i valori vanno codificati in modo tale che siano una stringa "valida" come url, inoltre se non ricordo male ha un limite di 2048 byte massimi.

Ad ogni modo se vuoi approfondire o per utilizzi futuri questo potrebbe essere un modo:

// Funzione per codificare i parametri URL
String urlEncode(const String& str) {
  String encoded = "";
  for (size_t i = 0; i < str.length(); i++) {
    char c = str[i];
    if (isalnum(c)) {
      encoded += c; // Lettere e numeri non richiedono codifica
    } else if (c == ' ') {
      encoded += '+'; // Gli spazi diventano "+"
    } else {
      char hex[4];
      sprintf(hex, "%%%02X", (unsigned char)c); // Codifica il carattere speciale
      encoded += hex;
    }
  }
  return encoded;
}

void sendLogData(const String& logData) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    NetworkClientSecure client;
    client.setInsecure();  // Disabilita la verifica del certificato

    String currentUrl = serverName; 
    currentUrl += "?data=";
    currentUrl += urlEncode(logData);

    Serial.println("Connecting to: " + currentUrl);
    http.begin(client, currentUrl);
    http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
    int httpResponseCode = http.GET();

    if (httpResponseCode > 0) {
      String response = http.getString();
      Serial.println(response);
    } else {
      // Errore HTTP
      Serial.println("Error: " + String(httpResponseCode));
    }
    http.end();  // Chiude la connessione dopo ogni iterazione
  }

  else {
    Serial.println("WiFi not connected");
  }
}