Save JPEG image to GoogleDrive[API] with ESP32 and OV2640 (Software)

In the Video

Japanese【日本語】

English

1. Overview

Using ESP32 and OV2640, I do electronic work and a program that saves JPEG images captured by the camera to GoogleDrive. In this case, I use the GoogleDrive API [API].

Outline: Please see “Save JPEG image to GoogleDrive[API] with ESP32 and OV2640 (Overview)

There are three major works as follows.

2. Development environment [Arduino]

Arduino is used for software development. Arduino is available for free and runs on personal computers such as Windows, Mac, and Linux.
The computer and ESP32 development board are connected with a micro USB cable.

The Arduino IDE settings are as follows.
Please select “ESP32 Dev Module” for the motherboard to be used this time. Motherboard package ESP32 uses “1.0.6”.

See below for ArduinoIDE settings such as “Partition Scheme”.

3. Program [Software]

This program works as below.

1) POST the refresh token, client ID, and secret key to get the access token.
2) POST the access token and image file information and upload the file.

Create a new file in ArduinoIDE and copy and paste the following program. Please rewrite the CHANGE REQUIRED parameter to your environment and then write it to ESP32.

//*************************************************************************
//  ESP32CamGdriveAPI Ver2022.5.2
//  Arduino Board : ESP32 by Espressif Systems ver 1.0.6
//  Written by IT-Taro
//***********************************************************************
#include <WiFiClientSecure.h>
#include "esp_camera.h"
// ##################### Setting[Google,Wi-Fi] #####################
String clientId           = "413$$[omission]$$googleusercontent.com"; // $$$ CHANGE REQUIRED $$$
String clientSecret       = "GOC$$[omission]$$mdgijDWEOzAIBl85PxxDQ"; // $$$ CHANGE REQUIRED $$$
String refreshToken       = "1//$$[omission]$$K_OQvtejx2P3V8JG8M-lk"; // $$$ CHANGE REQUIRED $$$
String driveFolder        = "1fp$$[omission]$$Ma9eVQasfsrrg3skub38q"; // Folder ID of GoogleDrive $$$ CHANGE REQUIRED $$$

const char *ssid        = "##### SSID #####"; // $$$ CHANGE REQUIRED $$$
const char *password    = "### PASSWORD ###"; // $$$ CHANGE REQUIRED $$$
// ###################################################################
const char* refreshServer = "oauth2.googleapis.com";
const char* refreshUri    = "/token";
const char* apiServer     = "www.googleapis.com";
const char* apiUri        = "/upload/drive/v3/files?uploadType=multipart";
String accessToken        = "";
// Pin Settings
const byte LED_PIN      = 4;  // Green LED
// CAMERA_MODEL_M5_UNIT_CAM
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23
#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21
WiFiClientSecure httpsClient;
camera_fb_t * fb;
bool ledFlag          = true; // LED Control Flag
int waitingTime      = 30000; // Wait 30 seconds to google response.
void setup() {
  // ####### Start Setup #######
  Serial.begin(115200);
  Serial.println();
  Serial.println("Start!"); 
  // ####### Setting of Camera #######
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_VGA;  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
  config.jpeg_quality = 10;
  config.fb_count = 1;
  // Newly Added! for "No PSRAM" Board "ESP32 ver 2.0.2" and "M5Stack-Unit-Cam"
  // ESP32 1.0.6 not required
  //config.fb_location = CAMERA_FB_IN_DRAM;
  
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(3000);
    ESP.restart();
  }
  // ####### Green LED #######
  pinMode ( LED_PIN, OUTPUT );
  // ####### Wi-Fi #######
  WiFi.begin ( ssid, password );
  while ( WiFi.status() != WL_CONNECTED ) { // Loop Up to Wi-Fi connection
    // Blinks every second Up to Wi-Fi connection
    ledFlag = !ledFlag;
    digitalWrite(LED_PIN, ledFlag);
    delay ( 1000 );
    Serial.print ( "." );
  }
  // Wi-Fi connection Successfull [Display IP address]
  Serial.print ( "Wi-Fi Connected! IP address: " );
  Serial.println ( WiFi.localIP() );
  Serial.println ( );
  // LED lights when Wi-Fi is connected
  digitalWrite ( LED_PIN, true );
  // ####### NTP setting #######
  configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
  // ####### Skip HTTPS Server certificate #######
  // Skip HTTPS Server certificate [ or need to set rootCA】
  httpsClient.setInsecure();//skip verification
  //httpsClient.setCACert(rootCA);// or set roootCA
  // ####### Get JPEG picture #######
  Serial.println("Start get JPG");
  getCameraJPEG();
  // ####### get Access Token #######
  Serial.println("Start get AccessToken");
  getAccessToken();
  // ####### Save JPEG to GoogleDrive #######
  Serial.println("Start Post GoogleDrive");
  postGoogleDriveByAPI();
  Serial.println("API Completed!!!");
}
// Loop task
void loop() {
  delay(100);
}
// Send JPEG by Http POST
void postGoogleDriveByAPI() {
  Serial.println("Connect to " + String(apiServer));
  if (httpsClient.connect(apiServer, 443)) {
    Serial.println("Connection successful");
    // Get Time for save file name
    struct tm timeInfo;
    char preFilename[16];
    getLocalTime(&timeInfo);
    sprintf(preFilename, "%04d%02d%02d_%02d%02d%02d",
          timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
          timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
    String saveFilename = "esp32_" + String(preFilename) + ".jpg";
    String metadata = "--foo_bar_baz\r\n"
                      "Content-Type: application/json; charset=UTF-8\r\n\r\n"
                      "{\"name\":\"" + saveFilename + "\",\"parents\":[\"" + driveFolder + "\"]}\r\n\r\n"; // parents:save folder
                      //"{\"name\":\"" + saveFilename + "\"}\r\n\r\n"; // parerents is Optional
    String startBoundry = "--foo_bar_baz\r\n"
                          "Content-Type:image/jpeg\r\n\r\n";
    String endBoundry   = "\r\n--foo_bar_baz--";
    unsigned long contentsLength = metadata.length() + startBoundry.length() + fb->len + endBoundry.length();
    String header = "POST " + String(apiUri) + " HTTP/1.1\r\n" +
                    "HOST: " + String(apiServer) + "\r\n" +
                    "Connection: close\r\n" +
                    "content-type: multipart/related; boundary=foo_bar_baz\r\n" +
                    "content-length: " + String(contentsLength) + "\r\n" +
                    "authorization: Bearer " + accessToken + "\r\n\r\n";
    Serial.println("Send JPEG DATA by API");
    httpsClient.print(header);
    httpsClient.print(metadata);
    httpsClient.print(startBoundry);
    // JPEG data is separated into 1000 bytes and POST
    unsigned long dataLength = fb->len;
    uint8_t*      bufAddr    = fb->buf;
    for(unsigned long i = 0; i < dataLength ;i=i+1000) {
      if ( (i + 1000) < dataLength ) {
        httpsClient.write(( bufAddr + i ), 1000);
      } else if (dataLength%1000 != 0) {
        httpsClient.write(( bufAddr + i ), dataLength%1000);
      }
    }
    httpsClient.print(endBoundry);
    Serial.println("Waiting for response.");
    long int StartTime=millis();
    while (!httpsClient.available()) {
      Serial.print(".");
      delay(100);
      if ((StartTime+waitingTime) < millis()) {
        Serial.println();
        Serial.println("No response.");
        break;
      }
    }
    Serial.println();
    while (httpsClient.available()) {
      Serial.print(char(httpsClient.read()));
    }
    /*Serial.println("Recieving Reply");
    while (httpsClient.connected()) {
      String retLine = httpsClient.readStringUntil('\n');
      //Serial.println("retLine:" + retLine);
      int okStartPos = retLine.indexOf("200 OK");
      if (okStartPos >= 0) {
        Serial.println("200 OK");
        break;
      }
    }*/
  } else {
    Serial.println("Connected to " + String(refreshServer) + " failed.");
  }
  httpsClient.stop();
}
// Update access token with refresh token (access token valid time is 1 hour)
void getAccessToken() {
  accessToken = "None";
  // ####### Get Access Token #######
  Serial.println("Connect to " + String(refreshServer));
  if (httpsClient.connect(refreshServer, 443)) {
    Serial.println("Connection successful");
    String body = "client_id="     + clientId     + "&" +
                  "client_secret=" + clientSecret + "&" +
                  "refresh_token=" + refreshToken + "&" +
                  "grant_type=refresh_token";
    // Send Header
    httpsClient.println("POST "  + String(refreshUri) + " HTTP/1.1");
    httpsClient.println("Host: " + String(refreshServer));
    httpsClient.println("content-length: " + (String)body.length());
    httpsClient.println("Content-Type: application/x-www-form-urlencoded");
    httpsClient.println();
    // Send Body
    httpsClient.println(body);
    Serial.println("Recieving Token");
    while (httpsClient.connected()) {
      String retLine = httpsClient.readStringUntil('\n');
      //Serial.println("retLine:" + retLine);
      int tokenStartPos = retLine.indexOf("access_token");
      if (tokenStartPos >= 0) {
        tokenStartPos     = retLine.indexOf("\"", tokenStartPos) + 1;
        tokenStartPos     = retLine.indexOf("\"", tokenStartPos) + 1;
        int tokenEndPos   = retLine.indexOf("\"", tokenStartPos);
        accessToken       = retLine.substring(tokenStartPos, tokenEndPos); 
        Serial.println("AccessToken:"+accessToken);
        break;
      }
    }
  } else {
    Serial.println("Connected to " + String(refreshServer) + " failed.");
  }
  httpsClient.stop();
  if (accessToken == "None") {
    Serial.println("Get AccessToken Failed. Restart ESP32!");
    delay(3000);
    ESP.restart();
  }
}
// JPEG image acquisition with OV2640
void getCameraJPEG(){
  // Shoot several times and adjust AE (automatic exposure)
  esp_camera_fb_get();
  esp_camera_fb_get();
  fb = esp_camera_fb_get();  // Get JPEG
  if (!fb) {
    Serial.printf("Camera capture failed");
  }
  // Shooting end processing
  esp_camera_fb_return(fb);
  Serial.printf("JPG: %uB ", (uint32_t)(fb->len));
}

When the above software has been written and operates normally, images will be acquired with the camera when the ESP32 is started and the files will be saved in GoogleDrive.
Check if the image file is saved in the GoogleDrive.

reference

OAuth 2.0 for TV and Limited-Input Device Applications  |  Authorization  |  Google for Developers

Comments

Copied title and URL