Uploading image to Google Drive (adapting esp32-Cam example)

travisstdenis
Posts: 2
Joined: Fri Jan 12, 2024 10:53 pm

Uploading image to Google Drive (adapting esp32-Cam example)

Postby travisstdenis » Fri Jan 12, 2024 10:58 pm

The aim of my project is to take a screen capture of a TFT and upload it to Google Drive. I am using the FeatherWing 3.5" with onboard SD and Feather Huzzah esp8266. I have all the code worked out to take and make the image and am now trying to adapt an espCam project that uploads, however, I'm running into challenges in doing so

The only examples I have found rely on esp32-Cam library functions that I don't know how to replicate for my environment. Namely these:

Code: Select all

typedef struct {
        uint8_t * buf;              /*!< Pointer to the pixel data */
        size_t len;                 /*!< Length of the buffer in bytes */
        size_t width;               /*!< Width of the buffer in pixels */
        size_t height;              /*!< Height of the buffer in pixels */
        pixformat_t format;         /*!< Format of the pixel data */
    } camera_fb_t;
Here is the part of the code that I need help on with full code below. This version follows the example https://github.com/gsampallo/esp32cam-g ... gdrive.ino.

Code: Select all

void saveCapturedImage(String filename) {
      Serial.println("Connect to " + String(host));
      client.setInsecure();
      
      if (client.connect(host, port)) {
        Serial.println("Client connection successful");
        
        bmpFile = SD.open(filename, FILE_READ);
      
        //char *input = (char *)fb->buf;
        char *input = (char *)bmpFile.read();
        //int fbLen = fb->len;
        int fbLen = sizeof(bmpFile);
        char output[base64_enc_len(3)];
        String imageFile = "";
        for (int i=0; i<fbLen; i++) {
          base64_encode(output, (input++), 3);
          if (i%3==0) imageFile += urlencode(String(output));
        }
        String Data = filename+mimeType+myImage;
        
        Serial.println("Send a captured image to Google Drive.");
        
        client.println("POST " + url + " HTTP/1.1");
        client.println("Host: " + String(host));
        client.println("Content-Length: " + String(Data.length()+imageFile.length()));
        client.println("Content-Type: application/x-www-form-urlencoded");
        client.println();
        
        client.print(Data);
        int Index;
        for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
          client.print(imageFile.substring(Index, Index+1000));
        }
        
        Serial.println("Waiting for response.");
        long int StartTime=millis();
        while (!client.available()) {
          Serial.print(".");
          delay(100);
          if ((StartTime+waitingTime) < millis()) {
            Serial.println();
            Serial.println("No response.");
            //If you have no response, maybe need a greater value of waitingTime
            break;
          }
        }
        Serial.println();   
        while (client.available()) {
          Serial.print(char(client.read()));
        }  
      } else {         
        Serial.println("Connected to " + String(host) + " failed.");
      }
      client.stop();
    }
This will successfully connect, however the following error message happens (with a long stream of what I assume is byte data )after a bit of chugging:

Code: Select all

Connect to script.google.com
    Client connection successful
    Fatal exception 28(LoadProhibitedCause):
    epc1=0x4020180b, epc2=0x00000000, epc3=0x00000000, excvaddr=0x00000042, depc=0x00000000
I think this comes down to what I've tried to do to adapt the esp32-Cam functions that are likely wrong (the commented out being the originals and my solve uncommented):

Code: Select all

//char *input = (char *)fb->buf;
    char *input = (char *)bmpFile.read();
    //int fbLen = fb->len;
    int fbLen = sizeof(bmpFile);
What I could use is some guidance on how to replicate what is going on in these two functions, as everything else in the examples is not specific to the adapted esp32-Cam example or library.



full code here:

Code: Select all

    #include <SPI.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_HX8357.h>
    #include <Adafruit_STMPE610.h>
    //#include <Adafruit_ImageReader.h>
    #include <ESP8266WiFi.h>
    #include <WiFiClientSecure.h>
    //#include "SdFat.h"
    #include "SD.h"
    #include <Bounce2.h>
    #include "time.h"
    #include <Arduino_JSON.h>
    #include "Base64.h"
    
    #define STMPE_CS 16
    #define TFT_CS   0
    #define TFT_DC   15
    #define SD_CS    2
    #define TFT_RST -1
    
    Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST);
    Adafruit_STMPE610 touch = Adafruit_STMPE610(STMPE_CS);
    
    File bmpFile;
    
    const char* ssid     = "mySSID";
    const char* password = "myPass";
    
    const char* NTP_SERVER = "pool.ntp.org";
    const char* TZ_INFO = "EST5EDT,M3.2.0,M11.1.00";
    // const long  gmtOffset = -18000;        //should be 5 * 3600 or 18000 (seconds)
    // const int   daylightOffset = 3600;   //Replace with your daylight offset (seconds)
    tm timeinfo;
    time_t now;
    long unsigned lastNTPtime;
    unsigned long lastEntryTime;
    char theDate[20];
    char fileDate[20];
    
    //google drive stuff
    WiFiClientSecure client;
    const char* host = "script.google.com";
    const uint16_t port = 443;
    String myDeploymentID = "string_of_nonsense";
    String url = "/macros/s/string_of_nonsense/exec";
    String myMainFolderName = "myFolder";
    String mimeType = "&mimetype=image/jpeg";
    String myImage = "&data=";
    int waitingTime = 30000; //Wait 30 seconds to google response.
    
    // This is calibration data for touchscreen at no rotation
    #define TS_MINX 120
    #define TS_MAXX 3950
    #define TS_MINY 120
    #define TS_MAXY 3950
    
    #define PENRADIUS 1
    unsigned long lastTouch = millis();
    int prevPt[] = {0,0,0};
    
    const int debounceDelay = 50;
    #define capPin 17
    Bounce captureButton = Bounce();
    #define clearPin 5
    Bounce clearButton = Bounce();
    #define menuPin 4
    Bounce menuButton = Bounce();
    
    const unsigned int penColor = 0x0000;    //Black
    const unsigned int bgColor = 0xF72F;     //#F3E779
    
    void setup() { 
      Serial.begin(115200);
    
      //pinMode(capPin, INPUT);
      captureButton.attach(capPin, INPUT);
      captureButton.interval(debounceDelay);
      //pinMode(clearPin, INPUT);
      clearButton.attach(clearPin, INPUT);
      clearButton.interval(debounceDelay);
      //pinMode(menuPin, INPUT);
      menuButton.attach(menuPin, INPUT);
      menuButton.interval(debounceDelay);
      
      tft.begin();
      //tft.setRotation(1);
      tft.fillScreen(bgColor);
      
      if (! touch.begin()) {
        Serial.println("STMPE not found!");
        while(1);
      }
      Serial.println("Waiting for touch sense");
    
      Serial.print("Initializing SD card...");
      if(!SD.begin(SD_CS)) {
      //if(!SD.begin(SD_CS, SD_SCK_MHZ(25))) { // ESP32 requires 25 MHz limit
        Serial.println(F("SD begin() failed"));
        for(;;); // Fatal error, do not continue
      }
    
      Serial.printf("Connecting to %s ", ssid);
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED) {
        delay(300);
        Serial.print(".");
      }
      Serial.println("CONNECTED to WIFI");
    
      configTime(0, 0, NTP_SERVER);
      setenv("TZ", TZ_INFO, 1);
      if(getNTPtime(5)) {  // wait up to 10sec to sync
      } else {
        Serial.println("Time not set");
        //ESP.restart();
      }
      lastNTPtime = time(&now);
      lastEntryTime = millis();
    }
    
    void loop() {
      captureButton.update();
      if(captureButton.fell()){
        bmpSave();
      }
    
      clearButton.update();
      if(clearButton.fell()){
        clearScreen();
      }
    
      menuButton.update();
      if(menuButton.fell()){
        menuCall();
      }
      
      uint16_t x, y, ptX, ptY;
      uint8_t z, ptZ;
      if (touch.touched()) {
        // read x & y & z;
        while (! touch.bufferEmpty()) {
          touch.readData(&x, &y, &z);
          // normal alignment with no setRotation
          ptX = map(x, TS_MINX, TS_MAXX, 0, tft.width());
          ptY = map(y, TS_MINY, TS_MAXY, 0, tft.height());
          if(millis() - lastTouch < 40){
            tft.drawLine(prevPt[0], prevPt[1], ptX, ptY, penColor);
            prevPt[0] = ptX;  prevPt[1] = ptY;  prevPt[2] = ptZ;
            lastTouch = millis();
          } else{
            prevPt[0] = ptX;  prevPt[1] = ptY;  prevPt[2] = ptZ;
            lastTouch = millis();
          }
        }
      }
      //delay(10);
    }
    
    void drawAngledLine(int x, int y, int x1, int y1) {
      float dx = (PENRADIUS+1/2.0) * (x-x1) / sqrt(sq(x-x1) + sq(y-y1));
      float dy = (PENRADIUS+1/2.0) * (y-y1) / sqrt(sq(x-x1) + sq(y-y1));
      tft.fillTriangle(x+dx, y-dy, x-dx, y+dy, x1+dx, y1-dy, HX8357_WHITE);
      tft.fillTriangle(x-dx, y+dy, x1-dx, y1+dy, x1+dx, y1-dy, HX8357_WHITE);
    }
    
    void clearScreen() {
      tft.fillScreen(bgColor);
    }
    
    void bmpSave() {
      uint32_t filesize, offset;
      uint16_t width = tft.width(), height = tft.height();
      unsigned long startTime;
      unsigned long elapsedTime;
        
      if(!SD.begin(SD_CS)){
        Serial.println(F("SD begin() failed"));
        for(;;); // Fatal error, do not continue
      }
    
      //String filename = getDate();
      //filename = filename + ".bmp";
      char filename[20] ;
      strcpy(filename, fileDate);
      strcat(filename, "_note001.bmp");
      while (SD.exists(filename)) { 
        String num = String(filename).substring(13, 16);
        int val = num.toInt();
        char stuff[20];
        sprintf(stuff, "num in string: %s | value: %d", num, val);
        val++;
        sprintf(filename, "%s_note%03d.bmp", fileDate, val);
      } 
      Serial.println(filename);
    
      bmpFile = SD.open(filename, FILE_WRITE);
      // On error hang up
      if (!bmpFile) for (;;);
      Serial.print("image: "); Serial.print(filename); Serial.println(" started processing");
      //
      // File header: 14 bytes
      bmpFile.write('B'); bmpFile.write('M');
      writeFour(14+40+12+width*height*2); // File size in bytes
      writeFour(0);
      writeFour(14+40+12);                // Offset to image data from start
      //
      // Image header: 40 bytes
      writeFour(40);                      // Header size
      writeFour(width);                   // Image width
      writeFour(height);                  // Image height
      writeTwo(1);                        // Planes
      writeTwo(16);                       // Bits per pixel
      writeFour(0);                       // Compression (none)
      writeFour(0);                       // Image size (0 for uncompressed)
      writeFour(0);                       // Preferred X resolution (ignore)
      writeFour(0);                       // Preferred Y resolution (ignore)
      writeFour(0);                       // Colour map entries (ignore)
      writeFour(0);                       // Important colours (ignore)
      Serial.println("header written");
    
      // Colour masks: 12 bytes
      writeFour(0b0000011111100000);      // Green
      writeFour(0b1111100000000000);      // Red
      writeFour(0b0000000000011111);      // Blue
      Serial.println("color mask applied");
    
      // Image data: width * height * 2 bytes
      for (int y=height-1; y>=0; y--) {
        for (int x=0; x<width; x++) {
          writeTwo(getPixel(x,y));    // Each row must be a multiple of four bytes
        }
      }
      Serial.println("finished encoding");
    
      // Close the file
      bmpFile.close();
      elapsedTime = millis() - startTime;
      Serial.println("file finished saving");
      Serial.print("the file took "); Serial.print(elapsedTime/1000); Serial.println(" secs to complete");
    
      //uploadFile(filename);
      saveCapturedImage(filename);
    
      // http://www.technoblogy.com/show?398X
    }
    
    void writeTwo (uint16_t word) {
      bmpFile.write(word & 0xFF); bmpFile.write((word >> 8) & 0xFF);
    }
    
    void writeFour (uint32_t word) {
      bmpFile.write(word & 0xFF); bmpFile.write((word >> 8) & 0xFF);
      bmpFile.write((word >> 16) & 0xFF); bmpFile.write((word >> 24) & 0xFF);
    }
    
    uint16_t getPixel(int x, int y) { // get pixel color code in rgb565 format
      tft.startWrite();    //needed for low-level methods.  CS active
      tft.setAddrWindow(x, y, 1, 1);
      tft.writeCommand(0x2E); // memory read command.  sets DC
      
      uint8_t r, g, b;
      r = tft.spiRead(); // discard dummy read
      r = tft.spiRead();
      g = tft.spiRead();
      b = tft.spiRead();
      tft.endWrite();    //needed for low-level methods.  CS idle
      
      return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
    
      //https://forum.arduino.cc/t/create-snapshot-of-3-5-tft-and-save-to-file-in-bitmap-format/391367/7
    }
    
    bool getNTPtime(int sec) {
      {
        uint32_t start = millis();
        do {
          time(&now);
          localtime_r(&now, &timeinfo);
          Serial.print(".");
          delay(10);
        } while (((millis() - start) <= (1000 * sec)) && (timeinfo.tm_year < (2016 - 1900)));
        if (timeinfo.tm_year <= (2016 - 1900)) return false;  // the NTP call was not successful
        Serial.print("now ");  Serial.println(now);
        char time_output[30];
        strftime(time_output, 30, "%a  %d-%m-%y %T", localtime(&now));
        strftime(theDate, 30, "%d-%m-%y", localtime(&now));
        strftime(fileDate, 30, "%b%d-%y", localtime(&now));
        Serial.println(time_output);
        Serial.println();
      }
      return true;
      //https://github.com/SensorsIot/NTP-time-for-ESP8266-and-ESP32/blob/master/NTP_Example/NTP_Example.ino
      //https://microcontrollerslab.com/current-date-time-esp8266-nodemcu-ntp-server/
      //https://arduino.stackexchange.com/questions/42922/get-hour-with-ctime-time-library-with-esp8266
    }
    
    void saveCapturedImage(String filename) {
      Serial.println("Connect to " + String(host));
      client.setInsecure();
      
      if (client.connect(host, port)) {
        Serial.println("Client connection successful");
        
        bmpFile = SD.open(filename, FILE_READ);
      
        //char *input = (char *)fb->buf;
        char *input = (char *)bmpFile.read();
        //int fbLen = fb->len;
        int fbLen = sizeof(bmpFile);
        char output[base64_enc_len(3)];
        String imageFile = "";
        for (int i=0; i<fbLen; i++) {
          base64_encode(output, (input++), 3);
          if (i%3==0) imageFile += urlencode(String(output));
        }
        String Data = filename+mimeType+myImage;
        
        Serial.println("Send a captured image to Google Drive.");
        
        client.println("POST " + url + " HTTP/1.1");
        client.println("Host: " + String(host));
        client.println("Content-Length: " + String(Data.length()+imageFile.length()));
        client.println("Content-Type: application/x-www-form-urlencoded");
        client.println();
        
        client.print(Data);
        int Index;
        for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
          client.print(imageFile.substring(Index, Index+1000));
        }
        
        Serial.println("Waiting for response.");
        long int StartTime=millis();
        while (!client.available()) {
          Serial.print(".");
          delay(100);
          if ((StartTime+waitingTime) < millis()) {
            Serial.println();
            Serial.println("No response.");
            //If you have no response, maybe need a greater value of waitingTime
            break;
          }
        }
        Serial.println();   
        while (client.available()) {
          Serial.print(char(client.read()));
        }  
      } else {         
        Serial.println("Connected to " + String(host) + " failed.");
      }
      client.stop();
    }
    
    String urlencode(String str) {
        String encodedString="";
        char c;
        char code0;
        char code1;
        char code2;
        for (int i =0; i < str.length(); i++){
          c=str.charAt(i);
          if (c == ' '){
            encodedString+= '+';
          } else if (isalnum(c)){
            encodedString+=c;
          } else{
            code1=(c & 0xf)+'0';
            if ((c & 0xf) >9){
                code1=(c & 0xf) - 10 + 'A';
            }
            c=(c>>4)&0xf;
            code0=c+'0';
            if (c > 9){
                code0=c - 10 + 'A';
            }
            code2='\0';
            encodedString+='%';
            encodedString+=code0;
            encodedString+=code1;
            //encodedString+=code2;
          }
          yield();
        }
        return encodedString;
    }

liaifat85
Posts: 200
Joined: Wed Dec 06, 2023 2:46 pm

Re: Uploading image to Google Drive (adapting esp32-Cam example)

Postby liaifat85 » Sat Jan 13, 2024 12:06 pm

Replace the following line:

Code: Select all

//char *input = (char *)fb->buf;
char *input = (char *)bmpFile.read();
with

Code: Select all

char *input = (char *)bmpFile.read(); // Read one byte at a time
and replace

Code: Select all

//int fbLen = fb->len;
int fbLen = sizeof(bmpFile);
with

Code: Select all

int fbLen = bmpFile.size(); // Get the size of the file
See if you can see any improvement.
Last edited by liaifat85 on Sat Jan 13, 2024 12:07 pm, edited 1 time in total.

travisstdenis
Posts: 2
Joined: Fri Jan 12, 2024 10:53 pm

Re: Uploading image to Google Drive (adapting esp32-Cam example)

Postby travisstdenis » Sun Jan 14, 2024 5:19 pm

What I need is a new function that replaces the ESP32-CAM function fb->buf. How can I create an image file buffer (which is what I assume fb->buf does) that is then used to Base64 encode later on?

Who is online

Users browsing this forum: No registered users and 10 guests